Compare commits
145 Commits
directus/s
...
27b8dc4118
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27b8dc4118 | ||
|
|
45a2627ec6 | ||
|
|
eba518ccc2 | ||
|
|
3cfe6697a9 | ||
|
|
07716dae17 | ||
|
|
db65ac52a3 | ||
|
|
fdc8a0aae6 | ||
|
|
092d2a4458 | ||
|
|
760281f7a4 | ||
|
|
157cb9389c | ||
|
|
be02d749dd | ||
|
|
feac162baa | ||
|
|
f86630bcb0 | ||
|
|
abaea70c7b | ||
|
|
1fde3d4f69 | ||
|
|
7b5a8bc705 | ||
|
|
d284010e28 | ||
|
|
66409ab859 | ||
|
|
46e705f6ea | ||
|
|
3bd4de2f30 | ||
|
|
00e4826744 | ||
|
|
2744e6173c | ||
|
|
915879beac | ||
|
|
6d4a62fae7 | ||
|
|
506a5ed14e | ||
|
|
2374a6bd22 | ||
|
|
8bc95d0f50 | ||
|
|
3485c4583d | ||
|
|
ee949aa76f | ||
|
|
07d2c8628f | ||
|
|
d39a98a42f | ||
|
|
525422105c | ||
|
|
a0473094cf | ||
|
|
36004bddb0 | ||
|
|
89bbbf5595 | ||
|
|
4ccbc9d9a8 | ||
|
|
b73066352e | ||
|
|
f95f792775 | ||
|
|
5c161b8381 | ||
|
|
47e50a3ba4 | ||
|
|
cf12428f98 | ||
|
|
67362dad96 | ||
|
|
82587a6211 | ||
|
|
c05b50877b | ||
|
|
9bf417478a | ||
|
|
4f955a9ec6 | ||
|
|
f1b0d269bf | ||
|
|
32c698c39a | ||
|
|
aa93600c79 | ||
|
|
0b45967d30 | ||
|
|
39c26e73c6 | ||
|
|
abd98ff21b | ||
|
|
3435819f79 | ||
|
|
cabdbd51cc | ||
|
|
1e3243aac3 | ||
|
|
54e53d278e | ||
|
|
f4319c4165 | ||
|
|
e6977ec7dd | ||
|
|
7e501c399b | ||
|
|
5cbc906d65 | ||
|
|
a6e3a48313 | ||
|
|
8afacdbe7a | ||
|
|
ad25836de7 | ||
|
|
5476783e0c | ||
|
|
56cdc5257d | ||
|
|
e841c4f433 | ||
|
|
0bb52c6818 | ||
|
|
e2598c58cf | ||
|
|
231131cc41 | ||
|
|
2940446f66 | ||
|
|
1c41348afe | ||
|
|
82c0905c0e | ||
|
|
1fd51a6a3f | ||
|
|
1e839680b4 | ||
|
|
d94f77a958 | ||
|
|
d89c832565 | ||
|
|
395d5d073a | ||
|
|
d2c04d58f2 | ||
|
|
0f05090b99 | ||
|
|
e179aa1743 | ||
|
|
edd6e54859 | ||
|
|
51afdcfb44 | ||
|
|
21135f4822 | ||
|
|
cd30b1e3df | ||
|
|
cce651531b | ||
|
|
c3973ddd1a | ||
|
|
486d6d9a20 | ||
|
|
dbb8d84143 | ||
|
|
0d9fe0eaec | ||
|
|
b24a02e292 | ||
|
|
8a1f03a7b7 | ||
|
|
eb5f4efd40 | ||
|
|
fac720c4a1 | ||
|
|
dcaf313745 | ||
|
|
0d329d1e9c | ||
|
|
d5c8b166bc | ||
|
|
266e5d1b35 | ||
|
|
94aa8c356e | ||
|
|
a5efc7415b | ||
|
|
50dc6ec4c0 | ||
|
|
423a4b74dd | ||
|
|
a0f2c93a23 | ||
|
|
d53df4b898 | ||
|
|
b6f3fdd15e | ||
|
|
4bb3fa3671 | ||
|
|
cb4cb9e578 | ||
|
|
6a14aca8ff | ||
|
|
21d5ba23a4 | ||
|
|
bc11be5669 | ||
|
|
4f3cc40041 | ||
|
|
ff811327bb | ||
|
|
6e26af71f4 | ||
|
|
6dcac27de8 | ||
|
|
c1b89c5823 | ||
|
|
ad73ab5672 | ||
|
|
44437c766b | ||
|
|
a5bc93f4d6 | ||
|
|
52d8102d67 | ||
|
|
8f2e3bcde1 | ||
|
|
f5c25dea75 | ||
|
|
4d220e1be7 | ||
|
|
3317926f61 | ||
|
|
4d56dce1af | ||
|
|
2411c6fdc8 | ||
|
|
a1b202686f | ||
|
|
8f0ede76f8 | ||
|
|
f971e84f17 | ||
|
|
640097c072 | ||
|
|
b62865cf04 | ||
|
|
403f8146d9 | ||
|
|
dc22676254 | ||
|
|
5ac9285248 | ||
|
|
e23c077a05 | ||
|
|
f914b7db1c | ||
|
|
770198bb5b | ||
|
|
025a84b2ef | ||
|
|
5caf0424cc | ||
|
|
3865b4b089 | ||
| 231b8cddc3 | |||
|
|
ea54db99a6 | ||
|
|
a4c05efa7a | ||
|
|
269f5c8eb9 | ||
|
|
3e03a9ab27 | ||
| 42e52b9811 | |||
| 95b3060759 |
2
astro/.env.example
Normal file
2
astro/.env.example
Normal file
@@ -0,0 +1,2 @@
|
||||
DIRECTUS_URL="https://"
|
||||
DIRECTUS_TOKEN=""
|
||||
24
astro/.gitignore
vendored
Normal file
24
astro/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# build output
|
||||
dist/
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
4
astro/.vscode/extensions.json
vendored
Normal file
4
astro/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"recommendations": ["astro-build.astro-vscode"],
|
||||
"unwantedRecommendations": []
|
||||
}
|
||||
11
astro/.vscode/launch.json
vendored
Normal file
11
astro/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"command": "./node_modules/.bin/astro dev",
|
||||
"name": "Development server",
|
||||
"request": "launch",
|
||||
"type": "node-terminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
43
astro/README.md
Normal file
43
astro/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Astro Starter Kit: Minimal
|
||||
|
||||
```sh
|
||||
npm create astro@latest -- --template minimal
|
||||
```
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
├── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||
|
||||
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||
|
||||
Any static assets, like images, can be placed in the `public/` directory.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
27
astro/astro.config.mjs
Normal file
27
astro/astro.config.mjs
Normal file
@@ -0,0 +1,27 @@
|
||||
// @ts-check
|
||||
import { defineConfig } from 'astro/config';
|
||||
import preact from '@astrojs/preact';
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
import graphql from '@rollup/plugin-graphql';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [preact()],
|
||||
output: 'static',
|
||||
prefetch: true,
|
||||
image: {
|
||||
domains: ['development.directus.itsfinniii.com']
|
||||
},
|
||||
vite: {
|
||||
plugins: [graphql(), tailwindcss()],
|
||||
resolve: {
|
||||
alias: {
|
||||
react: "preact/compat",
|
||||
"react-dom": "preact/compat",
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
exclude: ['@immich/justified-layout-wasm']
|
||||
}
|
||||
}
|
||||
});
|
||||
20
astro/changelogs.md
Normal file
20
astro/changelogs.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 1.0.0.0 - Release
|
||||
**Release date: **
|
||||
|
||||
- Add web pages with the following components:
|
||||
- Contact
|
||||
- Equipment Table
|
||||
- Frequently Asked Questions
|
||||
- Hero
|
||||
- Last Albums
|
||||
- Last Blogs
|
||||
- Last Projects
|
||||
- Reviews
|
||||
- Text with Side Image
|
||||
- Upcoming Events
|
||||
- Wall of Text
|
||||
- Add blogs
|
||||
- Add projects
|
||||
- Add photo categories, photo albums and photos
|
||||
- Add sitemaps
|
||||
- Add robots.txt
|
||||
6637
astro/package-lock.json
generated
Normal file
6637
astro/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
astro/package.json
Normal file
38
astro/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "astro",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/preact": "^5.1.2",
|
||||
"@directus/sdk": "^21.2.0",
|
||||
"@immich/justified-layout-wasm": "^0.4.3",
|
||||
"@rollup/plugin-graphql": "^2.0.5",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
"astro": "^6.1.9",
|
||||
"highlight.js": "^11.11.1",
|
||||
"markdown-it": "^14.1.1",
|
||||
"markdown-it-highlightjs": "^4.3.0",
|
||||
"md5": "^2.3.0",
|
||||
"mdast-util-to-string": "^4.0.0",
|
||||
"minify-xml": "^4.5.2",
|
||||
"preact": "^10.28.4",
|
||||
"react-responsive-masonry": "^2.7.2",
|
||||
"reading-time": "^1.5.0",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"overrides": {
|
||||
"vite": "^7.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/markdown-it": "^14.1.2",
|
||||
"@types/md5": "^2.3.6"
|
||||
}
|
||||
}
|
||||
BIN
astro/public/favicon.ico
Normal file
BIN
astro/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 655 B |
9
astro/public/favicon.svg
Normal file
9
astro/public/favicon.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 749 B |
1
astro/src/build/files.ts
Normal file
1
astro/src/build/files.ts
Normal file
@@ -0,0 +1 @@
|
||||
// This file gets files, and puts them in the public folder before starting the build.
|
||||
57
astro/src/components/blogs/BlogIndex.astro
Normal file
57
astro/src/components/blogs/BlogIndex.astro
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
import { getAllPaginatedBlogs } from '@/content/blogs/blogs';
|
||||
import { getSettings } from '@/content/settings/settings';
|
||||
import CalendarIcon from '@/icons/CalendarIcon.astro';
|
||||
import { getImageSize, getImageUrl } from '@/lib/images';
|
||||
import { markdownToHtml } from '@/lib/markdown';
|
||||
import { getBlogRoute } from '@/lib/routing';
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
interface Props {
|
||||
page: BlogIndex;
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const { pageNumber } = page;
|
||||
|
||||
const settings = await getSettings();
|
||||
const blogs = await getAllPaginatedBlogs(settings, pageNumber);
|
||||
---
|
||||
|
||||
<div
|
||||
id={`blogindex-${pageNumber}`}
|
||||
class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18 w-full"
|
||||
>
|
||||
<div class="flex flex-col justify-center items-center gap-2.5">
|
||||
<h1 class="text-5xl font-bold">{ settings.blog.title }</h1>
|
||||
{ settings.blog.subtext !== null && (
|
||||
<div set:html={markdownToHtml(settings.blog.subtext)}></div>
|
||||
) }
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
{ blogs.map((blog) => {
|
||||
const imageSize = getImageSize(blog.searchEngine.thumbnail.width, blog.searchEngine.thumbnail.height, 0.5);
|
||||
|
||||
return (
|
||||
<a href={getBlogRoute(settings.blog, blog)} class={`flex flex-col gap-2`}>
|
||||
<Image
|
||||
src={getImageUrl(blog.searchEngine.thumbnail.url)}
|
||||
alt={blog.title}
|
||||
class="flex rounded-2xl shadow-md w-full"
|
||||
width={imageSize.width}
|
||||
height={imageSize.height}
|
||||
/>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h4 class="font-semibold text-[28px]">{blog.title}</h4>
|
||||
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
|
||||
<CalendarIcon width={20} height={20} />
|
||||
<div>{blog.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
|
||||
}) }
|
||||
</div>
|
||||
</div>
|
||||
43
astro/src/components/blogs/BlogPost.astro
Normal file
43
astro/src/components/blogs/BlogPost.astro
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
import CalendarIcon from '@/icons/CalendarIcon.astro';
|
||||
import { getImageSize, getImageUrl } from '@/lib/images';
|
||||
import { markdownToHtml } from '@/lib/markdown';
|
||||
import { getTypographyClasses } from '@/styles/markdownClasses';
|
||||
import { Image } from 'astro:assets';
|
||||
import LastBlogs from '../web/LastBlogs.astro';
|
||||
|
||||
interface Props {
|
||||
blog: BlogPost;
|
||||
}
|
||||
|
||||
const { blog } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
id={`blog-${blog.id}`}
|
||||
class="flex flex-row justify-center items-center"
|
||||
>
|
||||
<div class="flex lg:flex-col flex-col py-12 px-12 gap-y-8 gap-x-18 lg:max-w-[50%]">
|
||||
<div class="flex flex-col gap-3">
|
||||
<h1 class="font-semibold text-5xl">{blog.title}</h1>
|
||||
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
|
||||
<CalendarIcon width={20} height={20} />
|
||||
<div>{blog.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="aspect-1200/630 w-full max-w-full overflow-hidden">
|
||||
<div class="w-full h-full rounded-2xl shadow-md object-cover">
|
||||
<Image
|
||||
src={blog.searchEngine.thumbnail.url}
|
||||
width={blog.searchEngine.thumbnail.width}
|
||||
height={blog.searchEngine.thumbnail.height}
|
||||
alt={blog.title}
|
||||
class="rounded-2xl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div set:html={markdownToHtml(blog.content)} class={`${getTypographyClasses()} min-w-full`}></div>
|
||||
</div>
|
||||
</div>
|
||||
24
astro/src/components/common/Pagination.astro
Normal file
24
astro/src/components/common/Pagination.astro
Normal file
@@ -0,0 +1,24 @@
|
||||
---
|
||||
interface Props {
|
||||
page: number;
|
||||
totalPages: number;
|
||||
urlTemplate: string;
|
||||
}
|
||||
|
||||
const { page, totalPages, urlTemplate } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="flex flex-row gap-2">
|
||||
{ totalPages < 7 && (
|
||||
<>
|
||||
{ [...Array(totalPages)].map((_: number, i: number) => (
|
||||
<a
|
||||
href={`${i + 1 === 1 ? urlTemplate : `${urlTemplate}/${i + 1}`}`}
|
||||
class={`flex select-none hover:cursor-pointer text-lg justify-center items-center
|
||||
${(i + 1 === page) ? "bg-(--ptc) text-(--ptt)" : "bg-neutral-200"} hover:bg-(--stc) hover:text-(--stt) duration-300 shadow-md rounded-full w-12 h-12`.trim()}>
|
||||
<span>{i + 1}</span>
|
||||
</a>
|
||||
)) }
|
||||
</>
|
||||
) }
|
||||
</div>
|
||||
72
astro/src/components/footer/Footer.astro
Normal file
72
astro/src/components/footer/Footer.astro
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
import { getFooter } from "@/content/footer/footer";
|
||||
import { Image } from "astro:assets";
|
||||
|
||||
const footer = await getFooter();
|
||||
---
|
||||
|
||||
<footer class="w-full mt-4">
|
||||
<div class="flex flex-col pt-12 pb-8 px-12 lg:container mx-auto">
|
||||
<div class="flex md:flex-row flex-col justify-center pt-12 pb-8 md:px-1 px-0 lg:container md:mx-auto md:gap-y-8 gap-y-12 md:gap-x-24 gap-x-0">
|
||||
{ (footer.title !== null || footer.logo !== null) && (
|
||||
<div class="flex flex-col gap-3">
|
||||
{ footer.title !== null && <h2 class="text-5xl font-bold">{footer.title}</h2>}
|
||||
{ footer.logo !== null && (
|
||||
<Image
|
||||
src={footer.logo.url}
|
||||
width={footer.logo.width}
|
||||
height={footer.logo.height}
|
||||
alt={footer.title ?? ""}
|
||||
class="md:w-50 w-[50%] h-auto"
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
) }
|
||||
|
||||
{ footer.columns.map((column) => (
|
||||
<div class="flex flex-col gap-3 w-fit">
|
||||
<h2 class="text-4xl font-bold">{column.title}</h2>
|
||||
<div class="flex flex-col gap-3 w-fit">
|
||||
{column.links.map((link) => (
|
||||
<a class="text-lg text-neutral-800 hover:text-(--ptc) duration-300" href={link.url}>
|
||||
<span class="w-fit">{link.text}</span>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)) }
|
||||
</div>
|
||||
|
||||
{ footer.socials !== null && (
|
||||
<div class="flex flex-row gap-3 justify-center">
|
||||
{ footer.socials.map((social) => (
|
||||
<a href={social.url} target="_blank" class="bg-neutral-300 hover:bg-neutral-200 duration-300 shadow-sm rounded-full">
|
||||
<div class="p-2">
|
||||
<Image
|
||||
src={social.icon.url}
|
||||
width={32}
|
||||
height={32}
|
||||
alt={social.name}
|
||||
/>
|
||||
</div>
|
||||
</a>
|
||||
)) }
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
|
||||
{ (footer.copyright !== null || footer.secondaryLinks !== null) && (
|
||||
<div class="border-t border-t-neutral-200 w-full bg-neutral-50">
|
||||
<div class="flex md:flex-row flex-col justify-between py-8 px-12 lg:container mx-auto gap-y-8 gap-x-24">
|
||||
{ footer.copyright !== null && (<div class="text-neutral-600">{footer.copyright}</div>) }
|
||||
{ footer.secondaryLinks !== null && (
|
||||
<div class="flex flex-row gap-1.5">
|
||||
{ footer.secondaryLinks.map((link) => (
|
||||
<a class="text-neutral-600 hover:text-(--ptc) duration-300 w-fit" href={link.url}>{link.text}</a>
|
||||
)) }
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
) }
|
||||
</footer>
|
||||
55
astro/src/components/photos/Album.astro
Normal file
55
astro/src/components/photos/Album.astro
Normal file
@@ -0,0 +1,55 @@
|
||||
---
|
||||
import { getAlbumRoute, getPhotoRoute } from '@/lib/routing';
|
||||
import { AlbumPhotos } from './Album.tsx';
|
||||
import { getImageSize, getImageUrl } from '@/lib/images';
|
||||
import { getSettings } from '@/content/settings/settings';
|
||||
import Pagination from '../common/Pagination.astro';
|
||||
|
||||
interface Props {
|
||||
page: PhotoAlbumPage;
|
||||
}
|
||||
|
||||
const settings = await getSettings();
|
||||
const album = Astro.props.page;
|
||||
const pageNumber = Astro.props.page.pageNumber;
|
||||
|
||||
const totalAlbumPages = Math.ceil(album.photos.length / settings.photo.album.perPage);
|
||||
const sliceStartNumber = (pageNumber - 1) * settings.photo.album.perPage;
|
||||
const sliceEndNumber = pageNumber * settings.photo.album.perPage;
|
||||
|
||||
const remappedPhotos: PhotoAlbumGalleryItem[] = [];
|
||||
|
||||
album.photos.slice(sliceStartNumber, sliceEndNumber).forEach((photo) => {
|
||||
const resizedImage = getImageSize(photo.photo.width, photo.photo.height, 0.756);
|
||||
|
||||
remappedPhotos.push({
|
||||
id: photo.id,
|
||||
url: getPhotoRoute(settings.photo, album, photo),
|
||||
photo: {
|
||||
url: getImageUrl(photo.photo.url),
|
||||
width: resizedImage.width,
|
||||
height: resizedImage.height
|
||||
},
|
||||
text: photo.text
|
||||
});
|
||||
});
|
||||
---
|
||||
|
||||
<div
|
||||
id={`album-${album.id}`}
|
||||
class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-10 gap-x-18 w-full"
|
||||
>
|
||||
<div class="flex flex-col gap-7">
|
||||
<h1 class="text-5xl font-bold">{album.title}</h1>
|
||||
|
||||
<AlbumPhotos client:only photos={remappedPhotos} />
|
||||
|
||||
{ totalAlbumPages > 1 && (
|
||||
<Pagination
|
||||
page={pageNumber}
|
||||
totalPages={totalAlbumPages}
|
||||
urlTemplate={getAlbumRoute(settings.photo, album)}
|
||||
/>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
87
astro/src/components/photos/Album.tsx
Normal file
87
astro/src/components/photos/Album.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks";
|
||||
import { JustifiedLayout } from '@immich/justified-layout-wasm';
|
||||
import { LoadingSpinner } from "@/icons/jsx/loadingSpinner";
|
||||
|
||||
export function AlbumPhotos(props: { photos: PhotoAlbumGalleryItem[] }) {
|
||||
const containerRef = useRef(null);
|
||||
|
||||
const [ hasMounted, setHasMounted ] = useState<boolean>(false);
|
||||
const [ layout, setLayout ] = useState<JustifiedLayout | null>(null);
|
||||
const [ containerWidth, setContainerWidth ] = useState<number | null>(null);
|
||||
const [ containerHeight, setContainerHeight ] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setHasMounted(true);
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!hasMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
setContainerWidth(entries[0].contentRect.width);
|
||||
});
|
||||
|
||||
if (containerRef.current) {
|
||||
observer.observe(containerRef.current);
|
||||
}
|
||||
|
||||
return () => observer.disconnect();
|
||||
}, [ hasMounted ]);
|
||||
|
||||
useEffect(() => {
|
||||
if (containerWidth === null || !hasMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const aspectRatios = new Float32Array(props.photos.map((photo => photo.photo.width / photo.photo.height)));
|
||||
|
||||
const justifiedLayout = new JustifiedLayout(aspectRatios, {
|
||||
rowHeight: 265,
|
||||
rowWidth: containerWidth,
|
||||
spacing: 10,
|
||||
heightTolerance: 0.11
|
||||
});
|
||||
|
||||
setContainerHeight(justifiedLayout.containerHeight);
|
||||
setLayout(justifiedLayout);
|
||||
}, [ containerWidth, hasMounted ])
|
||||
|
||||
return (
|
||||
<div ref={containerRef} id={`albumgallery`}>
|
||||
{ layout !== null ? (
|
||||
<div class="relative w-full" style={{ height: containerHeight }}>
|
||||
{ props.photos.map((photo, index: number) => {
|
||||
const layoutPosition = layout.getPosition(index);
|
||||
|
||||
return (
|
||||
<a
|
||||
href={photo.url}
|
||||
key={`photo-${index}`}
|
||||
class="group absolute overflow-hidden bg-neutral-200"
|
||||
style={{
|
||||
top: layoutPosition.top,
|
||||
left: layoutPosition.left,
|
||||
width: layoutPosition.width,
|
||||
height: layoutPosition.height
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={photo.photo.url}
|
||||
alt={photo.text ?? ""}
|
||||
class="group-hover:scale-[101.5%] duration-200 w-full h-full"
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
}) }
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex ">
|
||||
<LoadingSpinner width={50} height={50} />
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
49
astro/src/components/photos/Category.astro
Normal file
49
astro/src/components/photos/Category.astro
Normal file
@@ -0,0 +1,49 @@
|
||||
---
|
||||
import { getCategoryAlbums } from "@/content/photos/albums";
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import { getImageUrl } from "@/lib/images";
|
||||
import { getAlbumRoute } from "@/lib/routing";
|
||||
import { Image } from "astro:assets";
|
||||
|
||||
interface Props {
|
||||
category: PhotoCategory;
|
||||
}
|
||||
|
||||
const category = Astro.props.category;
|
||||
const settings = await getSettings();
|
||||
const categoryAlbums = await getCategoryAlbums(settings, category.category.url);
|
||||
---
|
||||
|
||||
<div
|
||||
id={`categoryindex`}
|
||||
class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-10 gap-x-18 w-full"
|
||||
>
|
||||
<div class="flex flex-col justify-center items-center gap-2.5">
|
||||
<h1 class="text-5xl font-bold">{categoryAlbums[0].category.title}</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-5">
|
||||
{ categoryAlbums.map((album) => (
|
||||
<div class="flex flex-row justify-center items-center">
|
||||
<a href={getAlbumRoute(settings.photo, album)} class="group relative block w-[70%] overflow-hidden rounded-2xl shadow-md">
|
||||
<div>
|
||||
<Image
|
||||
src={getImageUrl(album.thumbnail.url)}
|
||||
alt={album.title}
|
||||
width={album.thumbnail.width}
|
||||
height={album.thumbnail.height}
|
||||
class="rounded-2xl transition-transform duration-300 group-hover:scale-102"
|
||||
/>
|
||||
|
||||
<div class="absolute inset-0 bg-black/70 flex items-center justify-center p-6 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<h3 class="text-white text-5xl font-bold text-center tracking-tight">
|
||||
{album.title}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
|
||||
43
astro/src/components/photos/CategoryIndex.astro
Normal file
43
astro/src/components/photos/CategoryIndex.astro
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
import { getAllCategories } from "@/content/photos/categories";
|
||||
import { getSettings } from "@/content/settings/settings"
|
||||
import { getImageUrl } from "@/lib/images";
|
||||
import { getCategoryRoute } from "@/lib/routing";
|
||||
import { Image } from "astro:assets";
|
||||
|
||||
const settings = await getSettings();
|
||||
const categories = await getAllCategories(settings);
|
||||
---
|
||||
|
||||
<div
|
||||
id={`categoryindex`}
|
||||
class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-10 gap-x-18 w-full"
|
||||
>
|
||||
<div class="flex flex-col justify-center items-center gap-2.5">
|
||||
<h1 class="text-5xl font-bold">Categories</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-5">
|
||||
{ categories.map((category) => (
|
||||
<div class="flex flex-row justify-center items-center">
|
||||
<a href={getCategoryRoute(settings.photo, category)} class="group relative block w-[70%] overflow-hidden rounded-2xl shadow-md">
|
||||
<div>
|
||||
<Image
|
||||
src={getImageUrl(category.thumbnail.url)}
|
||||
alt={category.title}
|
||||
width={category.thumbnail.width}
|
||||
height={category.thumbnail.height}
|
||||
class="rounded-2xl transition-transform duration-300 group-hover:scale-102"
|
||||
/>
|
||||
|
||||
<div class="absolute inset-0 bg-black/70 flex items-center justify-center p-6 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<h3 class="text-white text-5xl font-bold text-center tracking-tight">
|
||||
{category.title}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
100
astro/src/components/photos/Photo.astro
Normal file
100
astro/src/components/photos/Photo.astro
Normal file
@@ -0,0 +1,100 @@
|
||||
---
|
||||
import { getAlbum } from '@/content/photos/albums';
|
||||
import { getSettings } from '@/content/settings/settings';
|
||||
import ChevronUp from '@/icons/ChevronUp.astro';
|
||||
import Download from '@/icons/Download.astro';
|
||||
import Close from '@/icons/Close.astro';
|
||||
import { getImageSize, getImageUrl } from '@/lib/images';
|
||||
import { getAlbumRoute, getPhotoRoute } from '@/lib/routing';
|
||||
import { getImage } from 'astro:assets';
|
||||
import { Image } from 'astro:assets';
|
||||
import { getPhotoHash } from '@/lib/hash';
|
||||
|
||||
interface Props {
|
||||
page: PhotoPage;
|
||||
}
|
||||
|
||||
const photo = Astro.props.page;
|
||||
|
||||
const settings = await getSettings();
|
||||
const album = await getAlbum(settings, photo.album.url);
|
||||
|
||||
const photoIndex = album.photos.findIndex(p => p.id === photo.id);
|
||||
|
||||
let previousUrl: string | null = null;
|
||||
let nextUrl: string | null = null;
|
||||
|
||||
// Check for previous photo
|
||||
if (photoIndex > 0) {
|
||||
previousUrl = getPhotoRoute(settings.photo, album, album.photos[photoIndex - 1]);
|
||||
}
|
||||
|
||||
// Check for next photo
|
||||
if (photoIndex + 1 < album.photos.length) {
|
||||
nextUrl = getPhotoRoute(settings.photo, album, album.photos[photoIndex + 1]);
|
||||
}
|
||||
|
||||
const albumPageNumber = Math.ceil((photoIndex + 1) / settings.photo.album.perPage);
|
||||
const returnUrl = albumPageNumber > 1
|
||||
? `${getAlbumRoute(settings.photo, album)}/${albumPageNumber}`
|
||||
: getAlbumRoute(settings.photo, album);
|
||||
|
||||
|
||||
const resizedImageSize = getImageSize(photo.photo.width, photo.photo.height, 1);
|
||||
const downloadImageSize = getImageSize(photo.photo.width, photo.photo.height, 5);
|
||||
|
||||
const downloadUrl = await getImage({
|
||||
src: getImageUrl(photo.photo.url),
|
||||
width: downloadImageSize.width,
|
||||
height: downloadImageSize.height,
|
||||
format: "jpeg",
|
||||
quality: 100
|
||||
});
|
||||
|
||||
const downloadFileName = `${album.url.replaceAll("/", "")}-${getPhotoHash(photo)}.jpeg`;
|
||||
---
|
||||
|
||||
<div class="h-screen flex flex-col justify-center items-center">
|
||||
<div class="flex flex-col justify-center items-center h-full">
|
||||
<Image
|
||||
src={getImageUrl(photo.photo.url)}
|
||||
width={resizedImageSize.width}
|
||||
height={resizedImageSize.height}
|
||||
alt={photo.text ?? ""}
|
||||
class="h-full w-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-6 absolute top-8 right-8 text-white py-2.5 px-5 bg-[#000000aa] rounded-full z-10">
|
||||
<a data-astro-prefetch href={downloadUrl.src} download={downloadFileName}>
|
||||
<Download width={36} height={36} />
|
||||
</a>
|
||||
<a data-astro-prefetch href={returnUrl}>
|
||||
<Close width={36} height={36} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{ photo.text !== null && (
|
||||
<div class="absolute bottom-0 text-white text-xl bg-[#000000aa] w-full px-20 py-8">{photo.text.trim()}</div>
|
||||
) }
|
||||
|
||||
{ previousUrl !== null && (
|
||||
<a
|
||||
data-astro-prefetch
|
||||
href={previousUrl}
|
||||
class="absolute left-8 text-white p-3 bg-[#000000aa] rounded-full z-10 rotate-270"
|
||||
>
|
||||
<ChevronUp width={28} height={28} />
|
||||
</a>
|
||||
) }
|
||||
|
||||
{ nextUrl !== null && (
|
||||
<a
|
||||
data-astro-prefetch
|
||||
href={nextUrl}
|
||||
class="absolute right-8 text-white p-3 bg-[#000000aa] rounded-full z-10 rotate-90"
|
||||
>
|
||||
<ChevronUp width={28} height={28} />
|
||||
</a>
|
||||
) }
|
||||
</div>
|
||||
57
astro/src/components/projects/ProjectIndex.astro
Normal file
57
astro/src/components/projects/ProjectIndex.astro
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
import { getSettings } from '@/content/settings/settings';
|
||||
import { getAllPaginatedProjects } from '@/content/projects/projects';
|
||||
import { markdownToHtml } from '@/lib/markdown';
|
||||
import { Image } from 'astro:assets';
|
||||
import { getProjectRoute } from '@/lib/routing';
|
||||
import CalendarIcon from '@/icons/CalendarIcon.astro';
|
||||
import { getImageSize, getImageUrl } from '@/lib/images';
|
||||
import { promise } from 'astro:schema';
|
||||
|
||||
interface Props {
|
||||
page: ProjectIndex;
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const { pageNumber } = page;
|
||||
|
||||
const settings = await getSettings();
|
||||
const projects = await getAllPaginatedProjects(settings, pageNumber);
|
||||
---
|
||||
|
||||
<div
|
||||
id={`projectindex-${pageNumber}`}
|
||||
class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18 w-full"
|
||||
>
|
||||
<div class="flex flex-col justify-center items-center gap-2.5">
|
||||
<h1 class="text-5xl font-bold">{ settings.project.title }</h1>
|
||||
{ settings.project.subtext !== null && (
|
||||
<div set:html={markdownToHtml(settings.project.subtext)}></div>
|
||||
) }
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-6">
|
||||
{ projects.map((project) => {
|
||||
const imageSize = getImageSize(project.searchEngine.thumbnail.width, project.searchEngine.thumbnail.height, 0.5);
|
||||
|
||||
return (
|
||||
<a href={getProjectRoute(settings.project, project)} class={`flex flex-col gap-2`}>
|
||||
<Image
|
||||
src={getImageUrl(project.searchEngine.thumbnail.url)}
|
||||
alt={project.title}
|
||||
class="flex rounded-2xl shadow-md w-full"
|
||||
width={imageSize.width}
|
||||
height={imageSize.height}
|
||||
/>
|
||||
<div class="flex flex-col gap-1">
|
||||
<h4 class="font-semibold text-[28px]">{project.title}</h4>
|
||||
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
|
||||
<CalendarIcon width={20} height={20} />
|
||||
<div>{project.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}) }
|
||||
</div>
|
||||
</div>
|
||||
44
astro/src/components/projects/ProjectPost.astro
Normal file
44
astro/src/components/projects/ProjectPost.astro
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
import CalendarIcon from '@/icons/CalendarIcon.astro';
|
||||
import { getImageSize, getImageUrl } from '@/lib/images';
|
||||
import { markdownToHtml } from '@/lib/markdown';
|
||||
import { getTypographyClasses } from '@/styles/markdownClasses';
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
interface Props {
|
||||
project: ProjectPost;
|
||||
}
|
||||
|
||||
const { project } = Astro.props;
|
||||
|
||||
const imageSize = getImageSize(project.searchEngine.thumbnail.width, project.searchEngine.thumbnail.height, 1);
|
||||
---
|
||||
|
||||
<div
|
||||
id={`project-${project.id}`}
|
||||
class="flex flex-row justify-center items-center"
|
||||
>
|
||||
<div class="flex lg:flex-col flex-col py-12 px-12 gap-y-8 gap-x-18 lg:max-w-[50%]">
|
||||
<div class="flex flex-col gap-3">
|
||||
<h1 class="font-semibold text-5xl">{project.title}</h1>
|
||||
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
|
||||
<CalendarIcon width={20} height={20} />
|
||||
<div>{project.date}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="aspect-1200/630 w-full max-w-full overflow-hidden">
|
||||
<div class="w-full h-full rounded-2xl shadow-md object-cover">
|
||||
<Image
|
||||
src={getImageUrl(project.searchEngine.thumbnail.url)}
|
||||
width={imageSize.width}
|
||||
height={imageSize.height}
|
||||
alt={project.title}
|
||||
class="rounded-2xl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div set:html={markdownToHtml(project.content)} class={`${getTypographyClasses()} min-w-full`}></div>
|
||||
</div>
|
||||
</div>
|
||||
34
astro/src/components/web/Contact.astro
Normal file
34
astro/src/components/web/Contact.astro
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
import { markdownToHtml } from '@/lib/markdown';
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
interface Props {
|
||||
contact: ContactComponent;
|
||||
}
|
||||
|
||||
const contact = Astro.props.contact;
|
||||
---
|
||||
|
||||
<div
|
||||
id={`contact-${contact.id}`}
|
||||
class="flex lg:flex-row flex-col lg:justify-center justify-center items-center py-12 px-12 lg:container mx-auto gap-y-8 lg:gap-x-28 gap-x-18 lg:text-left text-center"
|
||||
>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<h2 class="text-5xl font-bold">{contact.title}</h2>
|
||||
<div set:html={markdownToHtml(contact.text)}></div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
{ contact.methods.map((method) => (
|
||||
<a href={method.url} target="_blank" style={{ "--sc": method.color }} class="flex flex-row items-center gap-2 text-lg hover:text-(--sc) duration-200">
|
||||
<Image
|
||||
src={method.icon.url}
|
||||
alt={method.title}
|
||||
width="30"
|
||||
height="30"
|
||||
/>
|
||||
<span>{method.title}</span>
|
||||
</a>
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
45
astro/src/components/web/EquipmentTable.astro
Normal file
45
astro/src/components/web/EquipmentTable.astro
Normal file
@@ -0,0 +1,45 @@
|
||||
---
|
||||
import { markdownToHtml } from '@/lib/markdown';
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
interface Props {
|
||||
equipment: EquipmentTableComponent;
|
||||
}
|
||||
|
||||
const equipment = Astro.props.equipment;
|
||||
---
|
||||
|
||||
<div
|
||||
id={`equipment-${equipment.id}`}
|
||||
class="flex lg:flex-row flex-col lg:justify-center justify-center py-12 px-12 lg:container mx-auto gap-y-8 lg:gap-x-28 gap-x-18 lg:text-left text-center"
|
||||
>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<h2 class="text-5xl font-bold">{equipment.title}</h2>
|
||||
{ equipment.text !== null && (
|
||||
<div set:html={markdownToHtml(equipment.text)}></div>
|
||||
) }
|
||||
</div>
|
||||
<table class="w-fit text-lg">
|
||||
<tbody>
|
||||
{ equipment.items.map((item, index: number) => (
|
||||
<tr class="odd:bg-gray-100 even:bg-gray-50 my-2">
|
||||
<th class={`text-right pr-4 py-0.5 leading-tight ps-4 ${index === 0 && "rounded-tl-2xl"} ${(index + 1) === equipment.items.length && "rounded-bl-2xl shadow-sm"}`}>
|
||||
<div class="flex flex-row justify-end items-center gap-1.5">
|
||||
{ item.icon !== null && (
|
||||
<Image
|
||||
src={item.icon.url}
|
||||
alt={item.text}
|
||||
width="24"
|
||||
height="24"
|
||||
/>
|
||||
) }
|
||||
<span>{item.title}</span>
|
||||
</div>
|
||||
</th>
|
||||
<td class={`text-left leading-tight pe-4 ${index === 0 && "rounded-tr-2xl"} ${(index + 1) === equipment.items.length && "rounded-br-2xl shadow-sm"}`}>{item.text}</td>
|
||||
</tr>
|
||||
)) }
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
25
astro/src/components/web/FrequentlyAskedQuestions.astro
Normal file
25
astro/src/components/web/FrequentlyAskedQuestions.astro
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
import { markdownToHtml } from '@/lib/markdown';
|
||||
import { QuestionList } from '@/components/web/subcomponents/QuestionList.tsx';
|
||||
|
||||
interface Props {
|
||||
faq: FrequentlyAskedQuestionsComponent;
|
||||
}
|
||||
|
||||
const faq = Astro.props.faq;
|
||||
---
|
||||
|
||||
<div
|
||||
id={`faq-${faq.id}`}
|
||||
class="flex lg:flex-row flex-col justify-between py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18 w-full"
|
||||
>
|
||||
<div class="flex flex-col gap-2.5 lg:w-[50%] w-full">
|
||||
<h2 class="text-5xl font-bold">{faq.title}</h2>
|
||||
{ faq.text !== null && (
|
||||
<div set:html={markdownToHtml(faq.text)}></div>
|
||||
) }
|
||||
</div>
|
||||
<div class="lg:w-[50%] w-full">
|
||||
<QuestionList client:load questions={faq.questions} />
|
||||
</div>
|
||||
</div>
|
||||
32
astro/src/components/web/Hero.astro
Normal file
32
astro/src/components/web/Hero.astro
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
interface Props {
|
||||
hero: HeroComponent;
|
||||
}
|
||||
|
||||
const hero = Astro.props.hero;
|
||||
---
|
||||
|
||||
<div id={`hero-${hero.id}`} class="flex w-full">
|
||||
<div class="flex w-full h-screen static">
|
||||
<Image
|
||||
src={hero.backgroundImage.url}
|
||||
width={hero.backgroundImage.width}
|
||||
height={hero.backgroundImage.height}
|
||||
class="flex w-full h-screen object-cover z-1"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<div class="absolute flex items-center w-full h-screen bg-[#00000082] z-10">
|
||||
<div class="absolute text-white lg:left-40 lg:p-8 p-16 z-30">
|
||||
<div class="flex flex-col gap-2.5 text-left">
|
||||
<h1 class="text-white font-bold md:text-8xl sm:text-7xl text-6xl w-fit">{hero.title}</h1>
|
||||
{ hero.text !== null && (
|
||||
<div class="lg:text-2xl text-lg max-w-170">{hero.text}</div>
|
||||
) }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
95
astro/src/components/web/LastAlbums.astro
Normal file
95
astro/src/components/web/LastAlbums.astro
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
import { getLastAlbums } from "@/content/photos/albums";
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import CalendarIcon from "@/icons/CalendarIcon.astro";
|
||||
import { getImageSize, getImageUrl } from "@/lib/images";
|
||||
import { getAlbumRoute } from "@/lib/routing";
|
||||
import { Image } from "astro:assets";
|
||||
|
||||
interface Props {
|
||||
albums: LastGalleriesComponent;
|
||||
}
|
||||
|
||||
function calculateSizeClasses(amount: number, length: number) {
|
||||
if (amount === 2 || length <= 2) {
|
||||
return "lg:w-[45%] w-full";
|
||||
}
|
||||
else {
|
||||
return "lg:w-[31%] w-full";
|
||||
}
|
||||
}
|
||||
|
||||
const albums = Astro.props.albums;
|
||||
const settings = await getSettings();
|
||||
const lastAlbums = await getLastAlbums(albums.amount);
|
||||
const size = calculateSizeClasses(albums.amount, lastAlbums.length);
|
||||
---
|
||||
|
||||
{ (settings.photo.enabled && lastAlbums.length > 0) && (
|
||||
<div
|
||||
id={`lastalbums-${albums.id}`}
|
||||
class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18 w-full"
|
||||
>
|
||||
<div class="flex flex-row justify-between items-center w-full">
|
||||
<h2 class="text-4xl font-bold">{albums.title}</h2>
|
||||
<div>
|
||||
<a
|
||||
href={settings.photo.categoryIndex.indexRouteTemplate}
|
||||
class="text-(--ptt) bg-(--ptc) hover:text-(--stt) hover:bg-(--stc) duration-200 py-3 px-5 rounded-full text-lg"
|
||||
>
|
||||
{albums.readMoreButtonText}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{ lastAlbums.length >= 4 ? (
|
||||
<div class="grid lg:grid-cols-2 lg:grid-rows-2 grid-cols-1 grid-rows-4 gap-x-10 gap-y-8">
|
||||
{ lastAlbums.map((album) => {
|
||||
const imageSize = getImageSize(album.thumbnail.width, album.thumbnail.height, 0.5);
|
||||
|
||||
return (
|
||||
<a href={getAlbumRoute(settings.photo, album)} class={`w-full flex flex-col gap-2`}>
|
||||
<Image
|
||||
src={getImageUrl(album.thumbnail.url)}
|
||||
alt={album.title}
|
||||
class="flex rounded-2xl shadow-md w-full"
|
||||
width={imageSize.width}
|
||||
height={imageSize.height}
|
||||
/>
|
||||
<h4 class="font-semibold text-[28px]">{album.title}</h4>
|
||||
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
|
||||
<CalendarIcon width={20} height={20} />
|
||||
<div>{album.startDate}</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}) }
|
||||
</div>
|
||||
|
||||
) : (
|
||||
<div class="flex flex-col lg:flex-row lg:justify-between gap-y-6">
|
||||
{ lastAlbums.map((album) => {
|
||||
const imageSize = getImageSize(album.thumbnail.width, album.thumbnail.height, 0.5);
|
||||
|
||||
return (
|
||||
<a href={getAlbumRoute(settings.photo, album)} class={`${size} flex flex-col gap-2`}>
|
||||
<Image
|
||||
src={getImageUrl(album.thumbnail.url)}
|
||||
alt={album.title}
|
||||
class="flex rounded-2xl shadow-md w-full"
|
||||
width={imageSize.width}
|
||||
height={imageSize.height}
|
||||
/>
|
||||
<h4 class="font-semibold text-[28px]">{album.title}</h4>
|
||||
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
|
||||
<CalendarIcon width={20} height={20} />
|
||||
<div>{album.startDate}</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
|
||||
}) }
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
) }
|
||||
68
astro/src/components/web/LastBlogs.astro
Normal file
68
astro/src/components/web/LastBlogs.astro
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
import { getLastBlogs } from '@/content/blogs/blogs';
|
||||
import { getSettings } from '@/content/settings/settings';
|
||||
import CalendarIcon from '@/icons/CalendarIcon.astro';
|
||||
import { getImageSize, getImageUrl } from '@/lib/images';
|
||||
import { getBlogRoute } from '@/lib/routing';
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
interface Props {
|
||||
blogs: LastBlogsComponent;
|
||||
}
|
||||
|
||||
function calculateSizeClasses(amount: number, length: number) {
|
||||
if (amount === 2 || length <= 2) {
|
||||
return "lg:w-[45%] w-full";
|
||||
}
|
||||
else {
|
||||
return "lg:w-[31%] w-full";
|
||||
}
|
||||
}
|
||||
|
||||
const blogs = Astro.props.blogs;
|
||||
const settings = await getSettings();
|
||||
const lastBlogs = await getLastBlogs(blogs.amount);
|
||||
const size = calculateSizeClasses(blogs.amount, lastBlogs.length);
|
||||
---
|
||||
|
||||
{ (settings.blog.enabled && lastBlogs.length > 0) && (
|
||||
<div
|
||||
id={`lastblogs-${blogs.id}`}
|
||||
class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18 w-full"
|
||||
>
|
||||
<div class="flex flex-row justify-between items-center w-full">
|
||||
<h2 class="text-4xl font-bold">{blogs.title}</h2>
|
||||
<div>
|
||||
<a
|
||||
href={settings.blog.indexRouteTemplate}
|
||||
class="text-(--ptt) bg-(--ptc) hover:text-(--stt) hover:bg-(--stc) duration-200 py-3 px-5 rounded-full text-lg"
|
||||
>
|
||||
{blogs.readMoreButtonText}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col lg:flex-row lg:justify-between gap-y-6">
|
||||
{ lastBlogs.map((blog) => {
|
||||
const imageSize = getImageSize(blog.searchEngine.thumbnail.width, blog.searchEngine.thumbnail.height, 0.5);
|
||||
|
||||
return (
|
||||
<a href={getBlogRoute(settings.blog, blog)} class={`${size} flex flex-col gap-2`}>
|
||||
<Image
|
||||
src={getImageUrl(blog.searchEngine.thumbnail.url)}
|
||||
alt={blog.title}
|
||||
class="flex rounded-2xl shadow-md w-full"
|
||||
width={imageSize.width}
|
||||
height={imageSize.height}
|
||||
/>
|
||||
<h4 class="font-semibold text-[28px]">{blog.title}</h4>
|
||||
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
|
||||
<CalendarIcon width={20} height={20} />
|
||||
<div>{blog.date}</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}) }
|
||||
</div>
|
||||
</div>
|
||||
) }
|
||||
68
astro/src/components/web/LastProjects.astro
Normal file
68
astro/src/components/web/LastProjects.astro
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
import { getLastProjects } from '@/content/projects/projects';
|
||||
import { getSettings } from '@/content/settings/settings';
|
||||
import CalendarIcon from '@/icons/CalendarIcon.astro';
|
||||
import { getImageSize, getImageUrl } from '@/lib/images';
|
||||
import { getProjectRoute } from '@/lib/routing';
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
interface Props {
|
||||
projects: LastProjectsComponent;
|
||||
}
|
||||
|
||||
function calculateSizeClasses(amount: number, length: number) {
|
||||
if (amount === 2 || length <= 2) {
|
||||
return "lg:w-[45%] w-full";
|
||||
}
|
||||
else {
|
||||
return "lg:w-[31%] w-full";
|
||||
}
|
||||
}
|
||||
|
||||
const projects = Astro.props.projects;
|
||||
const settings = await getSettings();
|
||||
const lastProjects = await getLastProjects(projects.amount);
|
||||
const size = calculateSizeClasses(projects.amount, lastProjects.length);
|
||||
---
|
||||
|
||||
{ (settings.project.enabled && lastProjects.length > 0) && (
|
||||
<div
|
||||
id={`lastprojects-${projects.id}`}
|
||||
class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18 w-full"
|
||||
>
|
||||
<div class="flex flex-row justify-between items-center w-full">
|
||||
<h2 class="text-4xl font-bold">{projects.title}</h2>
|
||||
<div>
|
||||
<a
|
||||
href={settings.project.indexRouteTemplate}
|
||||
class="text-(--ptt) bg-(--ptc) hover:text-(--stt) hover:bg-(--stc) duration-200 py-3 px-5 rounded-full text-lg"
|
||||
>
|
||||
{projects.readMoreButtonText}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col lg:flex-row lg:justify-between gap-y-6">
|
||||
{ lastProjects.map((project) => {
|
||||
const imageSize = getImageSize(project.searchEngine.thumbnail.width, project.searchEngine.thumbnail.height, 0.5);
|
||||
|
||||
return (
|
||||
<a href={getProjectRoute(settings.project, project)} class={`${size} flex flex-col gap-2`}>
|
||||
<Image
|
||||
src={getImageUrl(project.searchEngine.thumbnail.url)}
|
||||
alt={project.title}
|
||||
class="flex rounded-2xl shadow-md w-full"
|
||||
width={imageSize.width}
|
||||
height={imageSize.height}
|
||||
/>
|
||||
<h4 class="font-semibold text-[28px]">{project.title}</h4>
|
||||
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
|
||||
<CalendarIcon width={20} height={20} />
|
||||
<div>{project.date}</div>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
}) }
|
||||
</div>
|
||||
</div>
|
||||
) }
|
||||
53
astro/src/components/web/Reviews.astro
Normal file
53
astro/src/components/web/Reviews.astro
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
import { markdownToHtml } from '@/lib/markdown';
|
||||
import StarRating from './subcomponents/StarRating.astro';
|
||||
|
||||
interface Props {
|
||||
reviews: ReviewListComponent;
|
||||
}
|
||||
|
||||
const reviews = Astro.props.reviews;
|
||||
|
||||
let totalStars: number = 0;
|
||||
const totalReviews: number = reviews.reviews.length;
|
||||
|
||||
reviews.reviews.forEach((review) => {
|
||||
totalStars = totalStars + review.stars;
|
||||
});
|
||||
|
||||
const averageStars = Math.round((totalStars / totalReviews) * 10) / 10;
|
||||
const reviewsToShow = reviews.reviews.slice(0, 5);
|
||||
---
|
||||
|
||||
<div
|
||||
id={`reviews-${reviews.id}`}
|
||||
class="flex lg:flex-row flex-col py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18 w-full"
|
||||
>
|
||||
<div class="flex flex-col lg:gap-5 gap-2.25 lg:min-w-[32.5%]">
|
||||
<div class="flex flex-col">
|
||||
<h2 class="text-3xl font-bold">{reviews.title}</h2>
|
||||
<div>{reviews.text}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5 text-lg">
|
||||
<StarRating size="big" stars={averageStars} />
|
||||
<p class="italic">{averageStars}</span> stars out of {totalReviews} {totalReviews === 1 ? "review" : "reviews"}.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-6.5">
|
||||
{ reviewsToShow.map((review) => (
|
||||
<div class="flex flex-col justify-center gap-3 bg-neutral-100 py-4 px-5.5 rounded-2xl shadow-sm">
|
||||
<div class="flex flex-col justify-center gap-1.25">
|
||||
<h4 class="text-2xl font-semibold">{review.name}</h4>
|
||||
<div>
|
||||
<StarRating size="small" stars={review.stars} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div set:html={markdownToHtml(review.review)}>
|
||||
</div>
|
||||
</div>
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
37
astro/src/components/web/TextWithImage.astro
Normal file
37
astro/src/components/web/TextWithImage.astro
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
import { markdownToHtml } from '@/lib/markdown';
|
||||
import { Image } from 'astro:assets';
|
||||
|
||||
interface Props {
|
||||
textWithImage: TextWithImageComponent;
|
||||
}
|
||||
|
||||
const textWithImage = Astro.props.textWithImage;
|
||||
|
||||
const imageSize = () => {
|
||||
if (textWithImage.imageSize === "small") return "aspect-2/1";
|
||||
else if (textWithImage.imageSize === "medium") return "aspect-26/17";
|
||||
else return "aspect-39/29";
|
||||
}
|
||||
---
|
||||
|
||||
<div
|
||||
id={`textwithimage-${textWithImage.id}`}
|
||||
class={`flex ${textWithImage.imageSide === "right" ? "lg:flex-row flex-col" : "lg:flex-row-reverse flex-col"} justify-between items-center py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18`}
|
||||
>
|
||||
<div class="flex flex-col gap-2.5 lg:w-[50%] w-full">
|
||||
<h2 class="text-5xl font-bold">{textWithImage.title}</h2>
|
||||
{ textWithImage.text !== null && (
|
||||
<div set:html={markdownToHtml(textWithImage.text)}></div>
|
||||
) }
|
||||
</div>
|
||||
<div class="lg:w-[50%] w-full">
|
||||
<Image
|
||||
src={textWithImage.image.url}
|
||||
width={textWithImage.image.width}
|
||||
height={textWithImage.image.height}
|
||||
class={`rounded-2xl shadow-sm ${imageSize()} object-cover`}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
57
astro/src/components/web/UpcomingEvents.astro
Normal file
57
astro/src/components/web/UpcomingEvents.astro
Normal file
@@ -0,0 +1,57 @@
|
||||
---
|
||||
import CalendarIcon from '@/icons/CalendarIcon.astro';
|
||||
import { Image } from 'astro:assets';
|
||||
import { upcomingEvent as UpcomingEvent } from './subcomponents/UpcomingEvent';
|
||||
import { markdownToHtml } from '@/lib/markdown';
|
||||
|
||||
interface Props {
|
||||
upcomingEvents: UpcomingEventsComponent;
|
||||
}
|
||||
|
||||
const upcomingEvents = Astro.props.upcomingEvents;
|
||||
---
|
||||
|
||||
<div
|
||||
id={`upcomingevents-${upcomingEvents.id}`}
|
||||
class="flex flex-col justify-between items-center py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18"
|
||||
>
|
||||
<h1 class="text-5xl font-bold">{upcomingEvents.title}</h1>
|
||||
|
||||
<div class="flex flex-col items-start gap-7 w-full">
|
||||
{ upcomingEvents.events.map((event) => (
|
||||
<div id={event.id} class="flex lg:flex-row flex-col items-center justify-center lg:gap-y-8 gap-y-5 gap-x-18">
|
||||
<div class="lg:w-[50%] w-full">
|
||||
<Image
|
||||
src={event.thumbnail.url}
|
||||
width={event.thumbnail.width}
|
||||
height={event.thumbnail.height}
|
||||
class={`aspect-15/9 rounded-2xl shadow-md object-cover`}
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5 lg:w-[40%] w-full">
|
||||
<h3 class="text-3xl font-semibold">{event.title}</h3>
|
||||
<div class="flex flex-row items-center gap-1.5 text-neutral-900">
|
||||
<CalendarIcon width={24} height={24} />
|
||||
{ event.endDate !== null ? (
|
||||
<p>{event.startDate} - {event.endDate}</p>
|
||||
) : (
|
||||
<p>{event.startDate}</p>
|
||||
) }
|
||||
</div>
|
||||
<div class="mt-3.5">
|
||||
<UpcomingEvent
|
||||
client:load
|
||||
event={{
|
||||
title: event.title,
|
||||
description: markdownToHtml(event.description),
|
||||
startDate: event.startDate,
|
||||
endDate: event.endDate
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)) }
|
||||
</div>
|
||||
</div>
|
||||
17
astro/src/components/web/WallOfText.astro
Normal file
17
astro/src/components/web/WallOfText.astro
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
import { markdownToHtml } from "@/lib/markdown";
|
||||
|
||||
interface Props {
|
||||
wallOfText: WallOfTextComponent;
|
||||
}
|
||||
|
||||
const wallOfText = Astro.props.wallOfText;
|
||||
---
|
||||
|
||||
<div
|
||||
id={`walloftext-${wallOfText.id}`}
|
||||
class="flex flex-col py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18 w-full"
|
||||
>
|
||||
<h2 class="text-5xl font-bold">{wallOfText.title}</h2>
|
||||
<div set:html={markdownToHtml(wallOfText.text)}></div>
|
||||
</div>
|
||||
37
astro/src/components/web/subcomponents/QuestionList.tsx
Normal file
37
astro/src/components/web/subcomponents/QuestionList.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { useState } from "preact/hooks";
|
||||
|
||||
export function QuestionList(props: { questions: FrequentlyAskedQuestion[]; }) {
|
||||
const [ open, setOpen ] = useState<number | null>(null);
|
||||
|
||||
const toggle = (index: number) => {
|
||||
setOpen(open === index ? null : index);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
{props.questions.map((question, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
onClick={() => toggle(index)}
|
||||
className={`w-full overflow-hidden border-l border-r border-t cursor-pointer ${open === index ? "bg-(--ptc) text-(--ptt)" : "bg-white"} ${index === 0 ? "rounded-t-2xl border-t border-(--ptc)" : ""} ${(index + 1) === props.questions.length ? "rounded-b-2xl border-b border-(--ptc) shadow-md" : "border-(--ptc)"}`}
|
||||
>
|
||||
<h4 className="text-lg font-semibold py-3 px-5 select-none">
|
||||
{question.question}
|
||||
</h4>
|
||||
|
||||
<div
|
||||
className={`grid transition-[grid-template-rows,opacity] duration-300 ease-in-out ${
|
||||
open === index ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"
|
||||
}`}
|
||||
>
|
||||
<div className="overflow-hidden">
|
||||
<div className="px-5 pb-5 text-sm text-gray-700">
|
||||
{question.answer}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
71
astro/src/components/web/subcomponents/StarRating.astro
Normal file
71
astro/src/components/web/subcomponents/StarRating.astro
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
interface Props {
|
||||
stars: number;
|
||||
size: "big" | "small";
|
||||
}
|
||||
|
||||
function roundToCustomHalf(value: number) {
|
||||
const whole = Math.floor(value);
|
||||
const decimal = value - whole;
|
||||
|
||||
if (decimal < 0.25) return whole;
|
||||
if (decimal < 0.75) return whole + 0.5;
|
||||
return whole + 1;
|
||||
}
|
||||
|
||||
const stars = roundToCustomHalf(Astro.props.stars);
|
||||
const totalStars = 5;
|
||||
---
|
||||
|
||||
<div class="flex flex-row gap-[4.25px]">
|
||||
{Array.from({ length: totalStars }).map((_, i) => {
|
||||
const starValue = i + 1;
|
||||
let type = "empty";
|
||||
|
||||
if (stars >= starValue) {
|
||||
type = "full";
|
||||
} else if (stars >= starValue - 0.5) {
|
||||
type = "half";
|
||||
}
|
||||
|
||||
return (
|
||||
<svg width={Astro.props.size === "big" ? 32 : 20} height={Astro.props.size === "big" ? 32 : 20} viewBox="3 0 24 24">
|
||||
{type === "full" && (
|
||||
<path
|
||||
fill="gold"
|
||||
d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22
|
||||
12 18.56 5.82 22 7 14.14 2 9.27
|
||||
8.91 8.26 12 2z"
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === "half" && (
|
||||
<>
|
||||
<defs>
|
||||
<linearGradient id={`half-${i}`}>
|
||||
<stop offset="50%" stop-color="gold" />
|
||||
<stop offset="50%" stop-color="lightgray" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
fill={`url(#half-${i})`}
|
||||
d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22
|
||||
12 18.56 5.82 22 7 14.14 2 9.27
|
||||
8.91 8.26 12 2z"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === "empty" && (
|
||||
<path
|
||||
fill="lightgray"
|
||||
d="M12 2l3.09 6.26L22 9.27l-5 4.87L18.18 22
|
||||
12 18.56 5.82 22 7 14.14 2 9.27
|
||||
8.91 8.26 12 2z"
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
51
astro/src/components/web/subcomponents/UpcomingEvent.tsx
Normal file
51
astro/src/components/web/subcomponents/UpcomingEvent.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { CalendarIcon } from "@/icons/jsx/calendarIcon";
|
||||
import { useState } from "preact/hooks"
|
||||
|
||||
export function upcomingEvent(props: { event: UpcomingEventProps }) {
|
||||
const [ isOpen, setIsOpen ] = useState<boolean>(false);
|
||||
const [ isVisible, setIsVisible ] = useState<boolean>(false);
|
||||
|
||||
const open = () => {
|
||||
setIsOpen(true);
|
||||
setTimeout(() => setIsVisible(true), 10); // allow DOM mount
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
setIsVisible(false);
|
||||
setTimeout(() => setIsOpen(false), 200); // match animation duration
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="hover:cursor-pointer text-indigo-400 underline w-fit text-lg font-semibold" onClick={open}>{"Read more >>"}</div>
|
||||
|
||||
{ isOpen && (
|
||||
<div
|
||||
onClick={close}
|
||||
className={`fixed inset-0 flex items-center justify-center bg-[#000000bb] shadow-md z-999999999999 duration-200 ${isVisible ? "opacity-100" : "opacity-0"}`}
|
||||
>
|
||||
<div
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className={`relative bg-white rounded-2xl shadow-xl max-w-md w-full p-6 transition-all duration-200 ${isVisible ? "opacity-100 scale-100" : "opacity-0 scale-95"}`}
|
||||
>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<h4 className="text-2xl font-semibold">{props.event.title}</h4>
|
||||
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
|
||||
<CalendarIcon width={24} height={24} />
|
||||
|
||||
{ props.event.endDate !== null ? (
|
||||
<p>{props.event.startDate} - {props.event.endDate}</p>
|
||||
) : (
|
||||
<p>{props.event.startDate}</p>
|
||||
) }
|
||||
</div>
|
||||
|
||||
<div className="mt-3" dangerouslySetInnerHTML={{ __html: props.event.description }}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
)
|
||||
}
|
||||
37
astro/src/components/webpage/Webpage.astro
Normal file
37
astro/src/components/webpage/Webpage.astro
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
import FrequentlyAskedQuestions from '../web/FrequentlyAskedQuestions.astro';
|
||||
import Hero from '../web/Hero.astro';
|
||||
import TextWithImage from '../web/TextWithImage.astro';
|
||||
import UpcomingEvents from '../web/UpcomingEvents.astro';
|
||||
import WallOfText from '../web/WallOfText.astro';
|
||||
import EquipmentTable from '../web/EquipmentTable.astro';
|
||||
import Reviews from '../web/Reviews.astro';
|
||||
import LastBlogs from '../web/LastBlogs.astro';
|
||||
import LastProjects from '../web/LastProjects.astro';
|
||||
import LastAlbums from '../web/LastAlbums.astro';
|
||||
import Contact from '../web/Contact.astro';
|
||||
|
||||
interface Props {
|
||||
webpage: WebpageComponent[];
|
||||
}
|
||||
|
||||
const components = Astro.props.webpage;
|
||||
---
|
||||
|
||||
<div class="flex flex-col">
|
||||
{ components.map((component) => (
|
||||
<Fragment>
|
||||
{ component.component === "Hero" && <Hero hero={component} /> }
|
||||
{ component.component === "TextWithImage" && <TextWithImage textWithImage={component} /> }
|
||||
{ component.component === "WallOfText" && <WallOfText wallOfText={component} /> }
|
||||
{ component.component === "UpcomingEvents" && <UpcomingEvents upcomingEvents={component} /> }
|
||||
{ component.component === "FrequentlyAskedQuestions" && <FrequentlyAskedQuestions faq={component} /> }
|
||||
{ component.component === "EquipmentTable" && <EquipmentTable equipment={component} /> }
|
||||
{ component.component === "Reviews" && <Reviews reviews={component} /> }
|
||||
{ component.component === "Contact" && <Contact contact={component} /> }
|
||||
{ component.component === "LastBlogs" && <LastBlogs blogs={component} /> }
|
||||
{ component.component === "LastProjects" && <LastProjects projects={component} /> }
|
||||
{ component.component === "LastGalleries" && <LastAlbums albums={component} /> }
|
||||
</Fragment>
|
||||
)) }
|
||||
</div>
|
||||
329
astro/src/content/blogs/blogs.ts
Normal file
329
astro/src/content/blogs/blogs.ts
Normal file
@@ -0,0 +1,329 @@
|
||||
import { createDirectusConnection } from "@/lib/directus";
|
||||
import { print } from 'graphql';
|
||||
import getBlogs from '@/graphql/blogs/getBlogs.graphql';
|
||||
import getBlogPost from '@/graphql/blogs/getBlog.graphql';
|
||||
import getLastBlogPosts from '@/graphql/blogs/getLastBlogPosts.graphql';
|
||||
import getPaginatedBlogs from '@/graphql/blogs/getPaginatedBlogs.graphql';
|
||||
import { formatDate } from "@/lib/dates";
|
||||
import { getImageSize, getImageUrl } from "@/lib/images";
|
||||
import { getImage } from "astro:assets";
|
||||
|
||||
export async function getAllBlogs(settings: GlobalSettings): Promise<BlogPost[]> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getBlogs), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D")
|
||||
});
|
||||
|
||||
let blogs: BlogPost[] = [];
|
||||
|
||||
result["Blogs"].forEach((blogRecord: any) => {
|
||||
let dates: string[] = [
|
||||
settings.blog.lastModified.toISOString(),
|
||||
settings.website.lastModified.toISOString(),
|
||||
blogRecord["date_created"],
|
||||
blogRecord["date_updated"],
|
||||
blogRecord["search_engine"][0]["date_created"],
|
||||
blogRecord["search_engine"][0]["date_updated"],
|
||||
blogRecord["search_engine"][0]["thumbnail"]["created_on"]
|
||||
];
|
||||
|
||||
const blogThumbnailImage =
|
||||
getImageSize(blogRecord["search_engine"][0]["thumbnail"]["width"], blogRecord["search_engine"][0]["thumbnail"]["height"], 0.756);
|
||||
|
||||
const blog: BlogPost = {
|
||||
exists: true,
|
||||
type: "BlogPost",
|
||||
id: blogRecord["id"],
|
||||
lastModified: new Date(),
|
||||
title: blogRecord["title"],
|
||||
content: blogRecord["content"],
|
||||
date: blogRecord["date"],
|
||||
url: blogRecord["url"],
|
||||
thumbnail: {
|
||||
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"],
|
||||
width: blogRecord["search_engine"][0]["thumbnail"]["width"],
|
||||
height: blogRecord["search_engine"][0]["thumbnail"]["height"]
|
||||
},
|
||||
searchEngine: {
|
||||
title: blogRecord["search_engine"][0]["title"],
|
||||
description: blogRecord["search_engine"][0]["description"],
|
||||
allowCrawlers: blogRecord["search_engine"][0]["allow_crawler"],
|
||||
canonical: blogRecord["search_engine"][0]["canonical"],
|
||||
priority: blogRecord["search_engine"][0]["priority"],
|
||||
thumbnail: {
|
||||
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"],
|
||||
width: blogThumbnailImage.width,
|
||||
height: blogThumbnailImage.height
|
||||
}
|
||||
},
|
||||
tags: []
|
||||
};
|
||||
|
||||
blogRecord["tags"].forEach((tagRecord: any) => {
|
||||
blog["tags"].push({
|
||||
text: tagRecord["Tags_id"]["text"],
|
||||
code: tagRecord["Tags_id"]["code"],
|
||||
color: tagRecord["Tags_id"]["color"]
|
||||
});
|
||||
|
||||
dates.push(tagRecord["Tags_id"]["date_created"]);
|
||||
dates.push(tagRecord["Tags_id"]["date_updated"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
blog.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
blog.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
blogs.push(blog);
|
||||
});
|
||||
|
||||
return blogs;
|
||||
}
|
||||
|
||||
export async function getBlog(settings: GlobalSettings, route: string): Promise<BlogPost> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getBlogPost), {
|
||||
route: route
|
||||
});
|
||||
|
||||
const blogRecord = result["Blogs"][0];
|
||||
|
||||
let dates: string[] = [
|
||||
settings.blog.lastModified.toISOString(),
|
||||
settings.website.lastModified.toISOString(),
|
||||
blogRecord["date_created"],
|
||||
blogRecord["date_updated"],
|
||||
blogRecord["search_engine"][0]["date_created"],
|
||||
blogRecord["search_engine"][0]["date_updated"],
|
||||
blogRecord["search_engine"][0]["thumbnail"]["created_on"]
|
||||
];
|
||||
|
||||
const blogThumbnailImage = getImageSize(blogRecord["search_engine"][0]["thumbnail"]["width"],
|
||||
blogRecord["search_engine"][0]["thumbnail"]["height"], 0.756);
|
||||
|
||||
const thumbnail = await getImage({
|
||||
src: getImageUrl(blogRecord["search_engine"][0]["thumbnail"]["filename_disk"]),
|
||||
width: blogThumbnailImage.width,
|
||||
height: blogThumbnailImage.height,
|
||||
format: "jpeg"
|
||||
});
|
||||
|
||||
const blog: BlogPost = {
|
||||
exists: true,
|
||||
type: "BlogPost",
|
||||
id: blogRecord["id"],
|
||||
lastModified: new Date(),
|
||||
title: blogRecord["title"],
|
||||
content: blogRecord["content"],
|
||||
date: blogRecord["date"],
|
||||
url: blogRecord["url"],
|
||||
thumbnail: {
|
||||
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"],
|
||||
width: blogRecord["search_engine"][0]["thumbnail"]["width"],
|
||||
height: blogRecord["search_engine"][0]["thumbnail"]["height"]
|
||||
},
|
||||
searchEngine: {
|
||||
title: blogRecord["search_engine"][0]["title"],
|
||||
description: blogRecord["search_engine"][0]["description"],
|
||||
allowCrawlers: blogRecord["search_engine"][0]["allow_crawler"],
|
||||
canonical: blogRecord["search_engine"][0]["canonical"],
|
||||
priority: blogRecord["search_engine"][0]["priority"],
|
||||
thumbnail: {
|
||||
url: `${settings.website.domainName}${thumbnail.src}`,
|
||||
width: blogThumbnailImage.width,
|
||||
height: blogThumbnailImage.height
|
||||
}
|
||||
},
|
||||
tags: []
|
||||
};
|
||||
|
||||
blogRecord["tags"].forEach((tagRecord: any) => {
|
||||
blog["tags"].push({
|
||||
text: tagRecord["Tags_id"]["text"],
|
||||
code: tagRecord["Tags_id"]["code"],
|
||||
color: tagRecord["Tags_id"]["color"]
|
||||
});
|
||||
|
||||
dates.push(tagRecord["Tags_id"]["date_created"]);
|
||||
dates.push(tagRecord["Tags_id"]["date_updated"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
blog.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
blog.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
return blog;
|
||||
}
|
||||
|
||||
export async function getLastBlogs(amount: number): Promise<BlogPost[]> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getLastBlogPosts), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D"),
|
||||
amount: amount
|
||||
});
|
||||
|
||||
let blogs: BlogPost[] = [];
|
||||
|
||||
result["Blogs"].forEach((blogRecord: any) => {
|
||||
let dates: string[] = [
|
||||
blogRecord["date_created"],
|
||||
blogRecord["date_updated"],
|
||||
blogRecord["search_engine"][0]["date_created"],
|
||||
blogRecord["search_engine"][0]["date_updated"],
|
||||
blogRecord["search_engine"][0]["thumbnail"]["created_on"]
|
||||
];
|
||||
|
||||
const blogThumbnailImage =
|
||||
getImageSize(blogRecord["search_engine"][0]["thumbnail"]["width"], blogRecord["search_engine"][0]["thumbnail"]["height"], 0.756);
|
||||
|
||||
const blog: BlogPost = {
|
||||
exists: true,
|
||||
type: "BlogPost",
|
||||
id: blogRecord["id"],
|
||||
lastModified: new Date(),
|
||||
title: blogRecord["title"],
|
||||
content: blogRecord["content"],
|
||||
date: blogRecord["date"],
|
||||
url: blogRecord["url"],
|
||||
thumbnail: {
|
||||
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"],
|
||||
width: blogRecord["search_engine"][0]["thumbnail"]["width"],
|
||||
height: blogRecord["search_engine"][0]["thumbnail"]["height"]
|
||||
},
|
||||
searchEngine: {
|
||||
title: blogRecord["search_engine"][0]["title"],
|
||||
description: blogRecord["search_engine"][0]["description"],
|
||||
allowCrawlers: blogRecord["search_engine"][0]["allow_crawler"],
|
||||
canonical: blogRecord["search_engine"][0]["canonical"],
|
||||
priority: blogRecord["search_engine"][0]["priority"],
|
||||
thumbnail: {
|
||||
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"],
|
||||
width: blogThumbnailImage.width,
|
||||
height: blogThumbnailImage.height
|
||||
}
|
||||
},
|
||||
tags: []
|
||||
};
|
||||
|
||||
blogRecord["tags"].forEach((tagRecord: any) => {
|
||||
blog["tags"].push({
|
||||
text: tagRecord["Tags_id"]["text"],
|
||||
code: tagRecord["Tags_id"]["code"],
|
||||
color: tagRecord["Tags_id"]["color"]
|
||||
});
|
||||
|
||||
dates.push(tagRecord["Tags_id"]["date_created"]);
|
||||
dates.push(tagRecord["Tags_id"]["date_updated"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
blog.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
blog.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
blogs.push(blog);
|
||||
});
|
||||
|
||||
return blogs;
|
||||
}
|
||||
|
||||
export async function getAllPaginatedBlogs(settings: GlobalSettings, page: number): Promise<BlogPost[]> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getPaginatedBlogs), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D"),
|
||||
limit: 8,
|
||||
pageNumber: page
|
||||
});
|
||||
|
||||
let blogs: BlogPost[] = [];
|
||||
|
||||
result["Blogs"].forEach((blogRecord: any) => {
|
||||
let dates: string[] = [
|
||||
settings.blog.lastModified.toISOString(),
|
||||
settings.website.lastModified.toISOString(),
|
||||
blogRecord["date_created"],
|
||||
blogRecord["date_updated"],
|
||||
blogRecord["search_engine"][0]["date_created"],
|
||||
blogRecord["search_engine"][0]["date_updated"],
|
||||
blogRecord["search_engine"][0]["thumbnail"]["created_on"]
|
||||
];
|
||||
|
||||
const blogThumbnailImage =
|
||||
getImageSize(blogRecord["search_engine"][0]["thumbnail"]["width"], blogRecord["search_engine"][0]["thumbnail"]["height"], 0.756);
|
||||
|
||||
const blog: BlogPost = {
|
||||
exists: true,
|
||||
type: "BlogPost",
|
||||
id: blogRecord["id"],
|
||||
lastModified: new Date(),
|
||||
title: blogRecord["title"],
|
||||
content: blogRecord["content"],
|
||||
date: blogRecord["date"],
|
||||
url: blogRecord["url"],
|
||||
thumbnail: {
|
||||
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"],
|
||||
width: blogRecord["search_engine"][0]["thumbnail"]["width"],
|
||||
height: blogRecord["search_engine"][0]["thumbnail"]["height"]
|
||||
},
|
||||
searchEngine: {
|
||||
title: blogRecord["search_engine"][0]["title"],
|
||||
description: blogRecord["search_engine"][0]["description"],
|
||||
allowCrawlers: blogRecord["search_engine"][0]["allow_crawler"],
|
||||
canonical: blogRecord["search_engine"][0]["canonical"],
|
||||
priority: blogRecord["search_engine"][0]["priority"],
|
||||
thumbnail: {
|
||||
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"],
|
||||
width: blogThumbnailImage.width,
|
||||
height: blogThumbnailImage.height
|
||||
}
|
||||
},
|
||||
tags: []
|
||||
};
|
||||
|
||||
blogRecord["tags"].forEach((tagRecord: any) => {
|
||||
blog["tags"].push({
|
||||
text: tagRecord["Tags_id"]["text"],
|
||||
code: tagRecord["Tags_id"]["code"],
|
||||
color: tagRecord["Tags_id"]["color"]
|
||||
});
|
||||
|
||||
dates.push(tagRecord["Tags_id"]["date_created"]);
|
||||
dates.push(tagRecord["Tags_id"]["date_updated"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
blog.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
blog.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
blogs.push(blog);
|
||||
});
|
||||
|
||||
return blogs;
|
||||
}
|
||||
110
astro/src/content/footer/footer.ts
Normal file
110
astro/src/content/footer/footer.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { createDirectusConnection } from "@/lib/directus";
|
||||
import { print } from 'graphql';
|
||||
import type { Footer, FooterColumn, FooterSecondaryLink, FooterSocial } from "@/types/footers/footer";
|
||||
import getFooterQuery from '@/graphql/footer/getFooter.graphql';
|
||||
import { getImageUrl } from "@/lib/images";
|
||||
|
||||
export async function getFooter(): Promise<Footer> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getFooterQuery));
|
||||
|
||||
const footerRecord = result['Footer'];
|
||||
|
||||
let dates: string[] = [
|
||||
footerRecord['date_created'],
|
||||
footerRecord['date_updated']
|
||||
];
|
||||
|
||||
let footer: Footer = {
|
||||
id: footerRecord['id'],
|
||||
title: footerRecord['title'],
|
||||
logo: {
|
||||
url: getImageUrl(footerRecord['logo']['filename_disk']),
|
||||
width: footerRecord['logo']['width'],
|
||||
height: footerRecord['logo']['height']
|
||||
},
|
||||
copyright: footerRecord['copyright'],
|
||||
columns: [],
|
||||
socials: null,
|
||||
secondaryLinks: null,
|
||||
lastModified: new Date()
|
||||
};
|
||||
|
||||
if (footerRecord['columns'] !== null) {
|
||||
footerRecord['columns'].forEach((footerColumn: any) => {
|
||||
const column: FooterColumn = {
|
||||
id: footerColumn['id'],
|
||||
title: footerColumn['title'],
|
||||
links: []
|
||||
};
|
||||
|
||||
footerColumn['links'].forEach((columnLink: any) => {
|
||||
column.links.push({
|
||||
id: columnLink['id'],
|
||||
text: columnLink['text'],
|
||||
url: columnLink['url']
|
||||
});
|
||||
|
||||
dates.push(columnLink['date_created']);
|
||||
dates.push(columnLink['date_updated']);
|
||||
});
|
||||
|
||||
footer.columns.push(column);
|
||||
|
||||
dates.push(footerColumn['date_created']);
|
||||
dates.push(footerColumn['date_updated']);
|
||||
});
|
||||
}
|
||||
|
||||
if (footerRecord['socials'] !== null) {
|
||||
let socials: FooterSocial[] = [];
|
||||
|
||||
footerRecord['socials'].forEach((footerSocial: any) => {
|
||||
socials.push({
|
||||
id: footerSocial['id'],
|
||||
name: footerSocial['name'],
|
||||
url: footerSocial['url'],
|
||||
icon: {
|
||||
url: getImageUrl(footerSocial['icon']['filename_disk']),
|
||||
width: footerSocial['icon']['width'],
|
||||
height: footerSocial['icon']['height']
|
||||
}
|
||||
});
|
||||
|
||||
dates.push(footerSocial['date_created']);
|
||||
dates.push(footerSocial['date_updated']);
|
||||
});
|
||||
|
||||
footer.socials = socials;
|
||||
}
|
||||
|
||||
if (footerRecord['secondary_links'] !== null) {
|
||||
let secondaryLinks: FooterSecondaryLink[] = [];
|
||||
|
||||
footerRecord['secondary_links'].forEach((footerSecondaryLink: any) => {
|
||||
secondaryLinks.push({
|
||||
id: footerSecondaryLink['id'],
|
||||
text: footerSecondaryLink['text'],
|
||||
url: footerSecondaryLink['url']
|
||||
});
|
||||
|
||||
dates.push(footerSecondaryLink['date_created']);
|
||||
dates.push(footerSecondaryLink['date_updated']);
|
||||
});
|
||||
|
||||
footer.secondaryLinks = secondaryLinks;
|
||||
}
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
footer.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
footer.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
return footer;
|
||||
}
|
||||
39
astro/src/content/menu/menu.ts
Normal file
39
astro/src/content/menu/menu.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { createDirectusConnection } from "@/lib/directus";
|
||||
import { print } from "graphql";
|
||||
import getMenuQuery from "@/graphql/menu/getMenu.graphql";
|
||||
|
||||
export async function getMenu(): Promise<Menu> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getMenuQuery));
|
||||
|
||||
const menuRecord = result['Menu'];
|
||||
|
||||
let menu: Menu = {
|
||||
id: menuRecord['id'],
|
||||
items: []
|
||||
};
|
||||
|
||||
menuRecord['items'].forEach((menuItem: any) => {
|
||||
if (menuItem['collection'] === "Menu_Column") {
|
||||
let menuColumnItem: MenuColumn = {
|
||||
id: menuItem['item']['id'],
|
||||
type: "Column",
|
||||
title: menuItem['item']['title'],
|
||||
links: []
|
||||
};
|
||||
|
||||
menuItem['item']['links'].forEach((menuItemLink: any) => {
|
||||
menuColumnItem.links.push({
|
||||
id: menuItemLink['id'],
|
||||
type: "Link",
|
||||
text: menuItemLink['text'],
|
||||
url: menuItemLink['url']
|
||||
});
|
||||
});
|
||||
|
||||
menu.items.push(menuColumnItem);
|
||||
}
|
||||
});
|
||||
|
||||
return menu;
|
||||
}
|
||||
377
astro/src/content/pages/pages.ts
Normal file
377
astro/src/content/pages/pages.ts
Normal file
@@ -0,0 +1,377 @@
|
||||
import { createDirectusConnection } from "@/lib/directus";
|
||||
import { print } from 'graphql';
|
||||
import { formatDate } from "@/lib/dates";
|
||||
import getAllPages from "@/graphql/pages/getAllPages.graphql";
|
||||
import getPage from "@/graphql/pages/getPage.graphql";
|
||||
import { getImageSize, getImageUrl } from "@/lib/images";
|
||||
|
||||
export function dataToPage(pageRecord: any): WebPage {
|
||||
let dates: string[] = [
|
||||
pageRecord["date_created"],
|
||||
pageRecord["date_updated"],
|
||||
pageRecord["search_engine"][0]["date_created"],
|
||||
pageRecord["search_engine"][0]["date_updated"]
|
||||
];
|
||||
|
||||
const searchEngine = pageRecord["search_engine"][0];
|
||||
|
||||
let components: WebpageComponent[] = [];
|
||||
|
||||
pageRecord["components"].forEach((componentRecord: any) => {
|
||||
const component = componentRecord["item"];
|
||||
|
||||
switch (componentRecord["item"]["__typename"]) {
|
||||
case "Hero":
|
||||
const resizedHeroImage =
|
||||
getImageSize(component["background_image"]["width"], component["background_image"]["height"], 2.5);
|
||||
|
||||
let heroComponent: HeroComponent = {
|
||||
component: "Hero",
|
||||
id: component["hero_id"],
|
||||
title: component["hero_title"],
|
||||
text: component["hero_text"],
|
||||
backgroundImage: {
|
||||
url: getImageUrl(component["background_image"]["filename_disk"]),
|
||||
width: resizedHeroImage.width,
|
||||
height: resizedHeroImage.height
|
||||
}
|
||||
};
|
||||
|
||||
components.push(heroComponent);
|
||||
dates.push(component["hero_created"]);
|
||||
dates.push(component["hero_updated"]);
|
||||
dates.push(component["background_image"]["created_on"]);
|
||||
|
||||
break;
|
||||
case "Text_With_Side_Image":
|
||||
const resizedTextWithSideImage =
|
||||
getImageSize(component["image"]["width"], component["image"]["height"], 1.5);
|
||||
|
||||
let textWithImageComponent: TextWithImageComponent = {
|
||||
component: "TextWithImage",
|
||||
id: component["twsi_id"],
|
||||
title: component["twsi_title"],
|
||||
text: component["twsi_text"],
|
||||
imageSide: component["twsi_image_side"],
|
||||
imageSize: component["twsi_image_size"],
|
||||
image: {
|
||||
url: getImageUrl(component["image"]["filename_disk"]),
|
||||
width: resizedTextWithSideImage.width,
|
||||
height: resizedTextWithSideImage.height
|
||||
}
|
||||
};
|
||||
|
||||
components.push(textWithImageComponent);
|
||||
dates.push(component["twsi_created"]);
|
||||
dates.push(component["twsi_updated"]);
|
||||
dates.push(component["image"]["created_on"]);
|
||||
|
||||
break;
|
||||
case "Wall_Of_Text":
|
||||
let wallOfTextComponent: WallOfTextComponent = {
|
||||
component: "WallOfText",
|
||||
id: component["wot_id"],
|
||||
title: component["wot_title"],
|
||||
text: component["wot_text"]
|
||||
};
|
||||
|
||||
components.push(wallOfTextComponent);
|
||||
dates.push(component["wot_created"]);
|
||||
dates.push(component["wot_updated"]);
|
||||
|
||||
break;
|
||||
case "Frequently_Asked_Questions":
|
||||
let faqComponent: FrequentlyAskedQuestionsComponent = {
|
||||
component: "FrequentlyAskedQuestions",
|
||||
id: component["faq_id"],
|
||||
title: component["faq_title"],
|
||||
text: component["faq_text"],
|
||||
questions: []
|
||||
};
|
||||
|
||||
component["questions"].forEach((faqQuestionRecord: any) => {
|
||||
faqComponent.questions.push({
|
||||
id: faqQuestionRecord["id"],
|
||||
question: faqQuestionRecord["question"],
|
||||
answer: faqQuestionRecord["answer"]
|
||||
});
|
||||
|
||||
dates.push(faqQuestionRecord["date_created"]);
|
||||
dates.push(faqQuestionRecord["date_updated"]);
|
||||
});
|
||||
|
||||
components.push(faqComponent);
|
||||
dates.push(component["faq_created"]);
|
||||
dates.push(component["faq_updated"]);
|
||||
|
||||
break;
|
||||
case "Upcoming_Events":
|
||||
let upcomingEventsComponent: UpcomingEventsComponent = {
|
||||
component: "UpcomingEvents",
|
||||
id: component["ue_id"],
|
||||
title: component["ue_title"],
|
||||
text: component["ue_text"],
|
||||
events: []
|
||||
};
|
||||
|
||||
component["events"].forEach((eventRecord: any) => {
|
||||
const resizedThumbnailImage =
|
||||
getImageSize(eventRecord["thumbnail"]["width"], eventRecord["thumbnail"]["height"], 1);
|
||||
|
||||
upcomingEventsComponent.events.push({
|
||||
id: eventRecord["id"],
|
||||
title: eventRecord["title"],
|
||||
description: eventRecord["description"],
|
||||
location: eventRecord["location"],
|
||||
mapLocation: [
|
||||
eventRecord["map_location"]["coordinates"][1],
|
||||
eventRecord["map_location"]["coordinates"][0]
|
||||
],
|
||||
thumbnail: {
|
||||
url: getImageUrl(eventRecord["thumbnail"]["filename_disk"]),
|
||||
width: resizedThumbnailImage.width,
|
||||
height: resizedThumbnailImage.height
|
||||
},
|
||||
startDate: eventRecord["start_date"],
|
||||
endDate: eventRecord["end_date"]
|
||||
});
|
||||
|
||||
dates.push(eventRecord["date_created"]);
|
||||
dates.push(eventRecord["date_updated"]);
|
||||
});
|
||||
|
||||
components.push(upcomingEventsComponent);
|
||||
dates.push(component["ue_created"]);
|
||||
dates.push(component["ue_updated"]);
|
||||
|
||||
break;
|
||||
case "Equipment_Table":
|
||||
let equipmentTableComponent: EquipmentTableComponent = {
|
||||
component: "EquipmentTable",
|
||||
id: component["et_id"],
|
||||
title: component["et_title"],
|
||||
text: component["et_text"],
|
||||
items: []
|
||||
};
|
||||
|
||||
component["items"].forEach((itemRecord: any) => {
|
||||
equipmentTableComponent.items.push({
|
||||
id: itemRecord["id"],
|
||||
title: itemRecord["title"],
|
||||
text: itemRecord["text"],
|
||||
icon: {
|
||||
url: getImageUrl(itemRecord["icon"]["filename_disk"]),
|
||||
width: itemRecord["icon"]["width"],
|
||||
height: itemRecord["icon"]["height"]
|
||||
}
|
||||
});
|
||||
|
||||
dates.push(itemRecord["date_created"]);
|
||||
dates.push(itemRecord["date_updated"]);
|
||||
dates.push(itemRecord["icon"]["created_on"]);
|
||||
});
|
||||
|
||||
components.push(equipmentTableComponent);
|
||||
dates.push(component["et_created"]);
|
||||
dates.push(component["et_updated"]);
|
||||
|
||||
break;
|
||||
case "Review_List":
|
||||
let reviewsComponent: ReviewListComponent = {
|
||||
component: "Reviews",
|
||||
id: component["rl_id"],
|
||||
title: component["rl_title"],
|
||||
text: component["rl_text"],
|
||||
reviews: []
|
||||
};
|
||||
|
||||
component["reviews"].forEach((reviewRecord: any) => {
|
||||
const reviewThumbnailImage =
|
||||
getImageSize(reviewRecord["thumbnail"]["width"], reviewRecord["thumbnail"]["height"], 1);
|
||||
|
||||
reviewsComponent.reviews.push({
|
||||
id: reviewRecord["id"],
|
||||
name: reviewRecord["name"],
|
||||
review: reviewRecord["review"],
|
||||
stars: reviewRecord["stars"],
|
||||
date: reviewRecord["date"],
|
||||
thumbnail: {
|
||||
url: getImageUrl(reviewRecord["thumbnail"]["filename_disk"]),
|
||||
width: reviewThumbnailImage.width,
|
||||
height: reviewThumbnailImage.height
|
||||
}
|
||||
});
|
||||
|
||||
dates.push(reviewRecord["date_created"]);
|
||||
dates.push(reviewRecord["date_updated"]);
|
||||
dates.push(reviewRecord["thumbnail"]["created_on"]);
|
||||
});
|
||||
|
||||
components.push(reviewsComponent);
|
||||
dates.push(component["rl_created"]);
|
||||
dates.push(component["rl_updated"]);
|
||||
|
||||
break;
|
||||
case "Contact":
|
||||
let contactComponent: ContactComponent = {
|
||||
component: "Contact",
|
||||
id: component["c_id"],
|
||||
title: component["c_title"],
|
||||
text: component["c_text"],
|
||||
methods: []
|
||||
};
|
||||
|
||||
component["methods"].forEach((contactMethodRecord: any) => {
|
||||
contactComponent.methods.push({
|
||||
id: contactMethodRecord["id"],
|
||||
title: contactMethodRecord["title"],
|
||||
url: contactMethodRecord["url"],
|
||||
color: contactMethodRecord["color"],
|
||||
icon: {
|
||||
url: getImageUrl(contactMethodRecord["icon"]["filename_disk"]),
|
||||
width: contactMethodRecord["icon"]["width"],
|
||||
height: contactMethodRecord["icon"]["height"]
|
||||
}
|
||||
});
|
||||
|
||||
dates.push(contactMethodRecord["date_created"]);
|
||||
dates.push(contactMethodRecord["date_updated"]);
|
||||
dates.push(contactMethodRecord["icon"]["created_on"]);
|
||||
});
|
||||
|
||||
components.push(contactComponent);
|
||||
dates.push(component["c_created"]);
|
||||
dates.push(component["c_updated"]);
|
||||
|
||||
break;
|
||||
case "Last_Blogs":
|
||||
let lastBlogsComponent: LastBlogsComponent = {
|
||||
component: "LastBlogs",
|
||||
id: component["lb_id"],
|
||||
title: component["lb_title"],
|
||||
readMoreButtonText: component["lb_read_more_button_text"],
|
||||
amount: component["lb_amount"]
|
||||
};
|
||||
|
||||
components.push(lastBlogsComponent);
|
||||
dates.push(component["lb_created"]);
|
||||
dates.push(component["lb_updated"]);
|
||||
|
||||
break;
|
||||
case "Last_Projects":
|
||||
let lastProjectsComponent: LastProjectsComponent = {
|
||||
component: "LastProjects",
|
||||
id: component["lp_id"],
|
||||
title: component["lp_title"],
|
||||
readMoreButtonText: component["lp_read_more_button_text"],
|
||||
amount: component["lp_amount"]
|
||||
};
|
||||
|
||||
components.push(lastProjectsComponent);
|
||||
dates.push(component["lp_created"]);
|
||||
dates.push(component["lp_updated"]);
|
||||
|
||||
break;
|
||||
case "Last_Galleries":
|
||||
let lastGalleriesComponent: LastGalleriesComponent = {
|
||||
component: "LastGalleries",
|
||||
id: component["lg_id"],
|
||||
title: component["lg_title"],
|
||||
readMoreButtonText: component["lg_read_more_button_text"],
|
||||
amount: component["lg_amount"]
|
||||
};
|
||||
|
||||
components.push(lastGalleriesComponent);
|
||||
dates.push(component["lg_created"]);
|
||||
dates.push(component["lg_updated"]);
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
let lastModified: Date;
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
const thumbnailImage =
|
||||
getImageSize(searchEngine["thumbnail"]["width"], searchEngine["thumbnail"]["width"], 0.756);
|
||||
|
||||
let page: WebPage = {
|
||||
type: "Webpage",
|
||||
exists: true,
|
||||
id: pageRecord["id"],
|
||||
lastModified: lastModified,
|
||||
url: pageRecord["url"],
|
||||
searchEngine: {
|
||||
title: searchEngine["title"],
|
||||
description: searchEngine["description"],
|
||||
canonical: searchEngine["canonical"],
|
||||
allowCrawlers: searchEngine["allow_crawler"],
|
||||
priority: searchEngine["priority"],
|
||||
thumbnail: {
|
||||
url: getImageUrl(searchEngine["thumbnail"]["filename_disk"]),
|
||||
height: thumbnailImage.height,
|
||||
width: thumbnailImage.width
|
||||
}
|
||||
},
|
||||
components: components
|
||||
}
|
||||
|
||||
return page;
|
||||
}
|
||||
|
||||
export async function getAllWebpages(): Promise<WebPage[]> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getAllPages), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D")
|
||||
});
|
||||
|
||||
let pages: WebPage[] = [];
|
||||
|
||||
result["Pages"].forEach((pageRecord: any) => {
|
||||
pages.push(dataToPage(pageRecord));
|
||||
});
|
||||
|
||||
return pages;
|
||||
}
|
||||
|
||||
export async function getWebpage(route: string): Promise<WebPage | null> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getPage), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D"),
|
||||
route: route
|
||||
});
|
||||
|
||||
if (result["Pages"].length === 0) {
|
||||
return {
|
||||
type: "Webpage",
|
||||
exists: false
|
||||
};
|
||||
}
|
||||
|
||||
const page = dataToPage(result["Pages"][0]);
|
||||
|
||||
if (!page.exists) {
|
||||
return {
|
||||
type: "Webpage",
|
||||
exists: false
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...page,
|
||||
type: "Webpage",
|
||||
exists: true
|
||||
}
|
||||
}
|
||||
347
astro/src/content/photos/albums.ts
Normal file
347
astro/src/content/photos/albums.ts
Normal file
@@ -0,0 +1,347 @@
|
||||
import { createDirectusConnection } from "@/lib/directus";
|
||||
import { print } from "graphql";
|
||||
import getAlbums from '@/graphql/photos/getAlbums.graphql';
|
||||
import getAlbumItem from '@/graphql/photos/getAlbum.graphql';
|
||||
import getLastAlbumsQuery from '@/graphql/photos/getLastAlbums.graphql';
|
||||
import getCategoryAlbumQuery from '@/graphql/photos/getCategoryAlbum.graphql';
|
||||
import { formatDate } from "@/lib/dates";
|
||||
import { getImageSize } from "@/lib/images";
|
||||
|
||||
export async function getAllAlbums(settings: GlobalSettings): Promise<PhotoAlbum[]> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getAlbums), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D")
|
||||
});
|
||||
|
||||
let albums: PhotoAlbum[] = [];
|
||||
|
||||
result["Photo_Albums"].forEach((albumRecord: any) => {
|
||||
let dates: string[] = [
|
||||
settings.website.lastModified.toISOString(),
|
||||
settings.photo.lastModified.toISOString(),
|
||||
albumRecord["date_created"],
|
||||
albumRecord["date_updated"],
|
||||
albumRecord["thumbnail"]["created_on"],
|
||||
];
|
||||
|
||||
const categoryThumbnailImage =
|
||||
getImageSize(albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["width"], albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["height"], 1.5);
|
||||
|
||||
const thumbnailImage =
|
||||
getImageSize(albumRecord["thumbnail"]["width"], albumRecord["thumbnail"]["height"], 0.756);
|
||||
|
||||
const album: PhotoAlbum = {
|
||||
exists: true,
|
||||
type: "PhotoAlbum",
|
||||
id: albumRecord["id"],
|
||||
title: albumRecord["title"],
|
||||
description: albumRecord["description"],
|
||||
url: albumRecord["url"],
|
||||
startDate: albumRecord["start_date"],
|
||||
endDate: albumRecord["end_date"],
|
||||
location: albumRecord["location"],
|
||||
category: {
|
||||
id: albumRecord["category"][0]["Photo_Categories_id"]["id"],
|
||||
title: albumRecord["category"][0]["Photo_Categories_id"]["title"],
|
||||
url: albumRecord["category"][0]["Photo_Categories_id"]["url"],
|
||||
thumbnail: {
|
||||
url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"],
|
||||
width: categoryThumbnailImage.width,
|
||||
height: categoryThumbnailImage.height
|
||||
}
|
||||
},
|
||||
thumbnail: {
|
||||
url: albumRecord["thumbnail"]["filename_disk"],
|
||||
width: thumbnailImage.width,
|
||||
height: thumbnailImage.height
|
||||
},
|
||||
photos: [],
|
||||
lastModified: new Date()
|
||||
};
|
||||
|
||||
albumRecord["photos"].forEach((photoRecord: any) => {
|
||||
album.photos.push({
|
||||
id: photoRecord["id"],
|
||||
photo: {
|
||||
url: photoRecord["photo"]["filename_disk"],
|
||||
width: photoRecord["photo"]["width"],
|
||||
height: photoRecord["photo"]["height"]
|
||||
},
|
||||
text: photoRecord["text"]
|
||||
});
|
||||
|
||||
dates.push(photoRecord["date_created"]);
|
||||
dates.push(photoRecord["date_updated"]);
|
||||
dates.push(photoRecord["photo"]["created_on"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
album.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
album.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
albums.push(album);
|
||||
});
|
||||
|
||||
return albums;
|
||||
}
|
||||
|
||||
export async function getAlbum(settings: GlobalSettings, route: string): Promise<PhotoAlbum> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getAlbumItem), {
|
||||
route: route
|
||||
});
|
||||
|
||||
const albumRecord = result["Photo_Albums"][0];
|
||||
|
||||
let dates: string[] = [
|
||||
settings.website.lastModified.toISOString(),
|
||||
settings.photo.lastModified.toISOString(),
|
||||
albumRecord["date_created"],
|
||||
albumRecord["date_updated"],
|
||||
albumRecord["thumbnail"]["created_on"],
|
||||
];
|
||||
|
||||
const categoryThumbnailImage =
|
||||
getImageSize(albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["width"], albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["height"], 1.5);
|
||||
|
||||
const thumbnailImage =
|
||||
getImageSize(albumRecord["thumbnail"]["width"], albumRecord["thumbnail"]["height"], 0.756);
|
||||
|
||||
const album: PhotoAlbum = {
|
||||
exists: true,
|
||||
type: "PhotoAlbum",
|
||||
id: albumRecord["id"],
|
||||
title: albumRecord["title"],
|
||||
description: albumRecord["description"],
|
||||
url: albumRecord["url"],
|
||||
startDate: albumRecord["start_date"],
|
||||
endDate: albumRecord["end_date"],
|
||||
location: albumRecord["location"],
|
||||
category: {
|
||||
id: albumRecord["category"][0]["Photo_Categories_id"]["id"],
|
||||
title: albumRecord["category"][0]["Photo_Categories_id"]["title"],
|
||||
url: albumRecord["category"][0]["Photo_Categories_id"]["url"],
|
||||
thumbnail: {
|
||||
url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"],
|
||||
width: categoryThumbnailImage.width,
|
||||
height: categoryThumbnailImage.height
|
||||
}
|
||||
},
|
||||
thumbnail: {
|
||||
url: albumRecord["thumbnail"]["filename_download"],
|
||||
width: thumbnailImage.width,
|
||||
height: thumbnailImage.height
|
||||
},
|
||||
photos: [],
|
||||
lastModified: new Date()
|
||||
};
|
||||
|
||||
albumRecord["photos"].forEach((photoRecord: any) => {
|
||||
album.photos.push({
|
||||
id: photoRecord["id"],
|
||||
photo: {
|
||||
url: photoRecord["photo"]["filename_disk"],
|
||||
width: photoRecord["photo"]["width"],
|
||||
height: photoRecord["photo"]["height"]
|
||||
},
|
||||
text: photoRecord["text"]
|
||||
});
|
||||
|
||||
dates.push(photoRecord["date_created"]);
|
||||
dates.push(photoRecord["date_updated"]);
|
||||
dates.push(photoRecord["photo"]["created_on"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
album.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
album.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
return album;
|
||||
}
|
||||
|
||||
export async function getLastAlbums(amount: number) {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getLastAlbumsQuery), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D"),
|
||||
limit: amount
|
||||
});
|
||||
|
||||
let albums: PhotoAlbum[] = [];
|
||||
|
||||
result["Photo_Albums"].forEach((albumRecord: any) => {
|
||||
let dates: string[] = [
|
||||
albumRecord["date_created"],
|
||||
albumRecord["date_updated"],
|
||||
albumRecord["thumbnail"]["created_on"],
|
||||
];
|
||||
|
||||
const categoryThumbnailImage =
|
||||
getImageSize(albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["width"], albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["height"], 1.5);
|
||||
|
||||
const thumbnailImage =
|
||||
getImageSize(albumRecord["thumbnail"]["width"], albumRecord["thumbnail"]["height"], 0.756);
|
||||
|
||||
const album: PhotoAlbum = {
|
||||
exists: true,
|
||||
type: "PhotoAlbum",
|
||||
id: albumRecord["id"],
|
||||
title: albumRecord["title"],
|
||||
description: albumRecord["description"],
|
||||
url: albumRecord["url"],
|
||||
startDate: albumRecord["start_date"],
|
||||
endDate: albumRecord["end_date"],
|
||||
location: albumRecord["location"],
|
||||
category: {
|
||||
id: albumRecord["category"][0]["Photo_Categories_id"]["id"],
|
||||
title: albumRecord["category"][0]["Photo_Categories_id"]["title"],
|
||||
url: albumRecord["category"][0]["Photo_Categories_id"]["url"],
|
||||
thumbnail: {
|
||||
url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"],
|
||||
width: categoryThumbnailImage.width,
|
||||
height: categoryThumbnailImage.height
|
||||
}
|
||||
},
|
||||
thumbnail: {
|
||||
url: albumRecord["thumbnail"]["filename_disk"],
|
||||
width: thumbnailImage.width,
|
||||
height: thumbnailImage.height
|
||||
},
|
||||
photos: [],
|
||||
lastModified: new Date()
|
||||
};
|
||||
|
||||
albumRecord["photos"].forEach((photoRecord: any) => {
|
||||
album.photos.push({
|
||||
id: photoRecord["id"],
|
||||
photo: {
|
||||
url: photoRecord["photo"]["filename_disk"],
|
||||
width: photoRecord["photo"]["width"],
|
||||
height: photoRecord["photo"]["height"]
|
||||
},
|
||||
text: photoRecord["text"]
|
||||
});
|
||||
|
||||
dates.push(photoRecord["date_created"]);
|
||||
dates.push(photoRecord["date_updated"]);
|
||||
dates.push(photoRecord["photo"]["created_on"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
album.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
album.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
albums.push(album);
|
||||
});
|
||||
|
||||
return albums;
|
||||
}
|
||||
|
||||
export async function getCategoryAlbums(settings: GlobalSettings, route: string): Promise<PhotoAlbum[]> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getCategoryAlbumQuery), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D"),
|
||||
categoryUrl: route
|
||||
});
|
||||
|
||||
let albums: PhotoAlbum[] = [];
|
||||
|
||||
result["Photo_Albums"].forEach((albumRecord: any) => {
|
||||
let dates: string[] = [
|
||||
settings.website.lastModified.toISOString(),
|
||||
settings.photo.lastModified.toISOString(),
|
||||
albumRecord["date_created"],
|
||||
albumRecord["date_updated"],
|
||||
albumRecord["thumbnail"]["created_on"],
|
||||
];
|
||||
|
||||
const categoryThumbnailImage =
|
||||
getImageSize(albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["width"], albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["height"], 1.5);
|
||||
|
||||
const thumbnailImage =
|
||||
getImageSize(albumRecord["thumbnail"]["width"], albumRecord["thumbnail"]["height"], 0.756);
|
||||
|
||||
const album: PhotoAlbum = {
|
||||
exists: true,
|
||||
type: "PhotoAlbum",
|
||||
id: albumRecord["id"],
|
||||
title: albumRecord["title"],
|
||||
description: albumRecord["description"],
|
||||
url: albumRecord["url"],
|
||||
startDate: albumRecord["start_date"],
|
||||
endDate: albumRecord["end_date"],
|
||||
location: albumRecord["location"],
|
||||
category: {
|
||||
id: albumRecord["category"][0]["Photo_Categories_id"]["id"],
|
||||
title: albumRecord["category"][0]["Photo_Categories_id"]["title"],
|
||||
url: albumRecord["category"][0]["Photo_Categories_id"]["url"],
|
||||
thumbnail: {
|
||||
url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"],
|
||||
width: categoryThumbnailImage.width,
|
||||
height: categoryThumbnailImage.height
|
||||
}
|
||||
},
|
||||
thumbnail: {
|
||||
url: albumRecord["thumbnail"]["filename_disk"],
|
||||
width: thumbnailImage.width,
|
||||
height: thumbnailImage.height
|
||||
},
|
||||
photos: [],
|
||||
lastModified: new Date()
|
||||
};
|
||||
|
||||
albumRecord["photos"].forEach((photoRecord: any) => {
|
||||
const imageSize =
|
||||
getImageSize(photoRecord["photo"]["width"], photoRecord["photo"]["height"], 0.8);
|
||||
|
||||
album.photos.push({
|
||||
id: photoRecord["id"],
|
||||
photo: {
|
||||
url: photoRecord["photo"]["filename_disk"],
|
||||
width: photoRecord["photo"]["width"],
|
||||
height: photoRecord["photo"]["height"]
|
||||
},
|
||||
text: photoRecord["text"]
|
||||
});
|
||||
|
||||
dates.push(photoRecord["date_created"]);
|
||||
dates.push(photoRecord["date_updated"]);
|
||||
dates.push(photoRecord["photo"]["created_on"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
album.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
album.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
albums.push(album);
|
||||
});
|
||||
|
||||
return albums;
|
||||
}
|
||||
56
astro/src/content/photos/categories.ts
Normal file
56
astro/src/content/photos/categories.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { createDirectusConnection } from "@/lib/directus";
|
||||
import { print } from "graphql";
|
||||
import getCategories from '@/graphql/photos/getCategories.graphql';
|
||||
import getCategory from '@/graphql/photos/getCategory.graphql';
|
||||
import { getImageSize } from "@/lib/images";
|
||||
|
||||
export async function getAllCategories(settings: GlobalSettings): Promise<PhotoAlbumCategory[]> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getCategories));
|
||||
|
||||
let categories: PhotoAlbumCategory[] = [];
|
||||
|
||||
result["Photo_Categories"].forEach((photoCategoryRecord: any) => {
|
||||
const imageSize =
|
||||
getImageSize(photoCategoryRecord["thumbnail"]["width"], photoCategoryRecord["thumbnail"]["height"], 1.5);
|
||||
|
||||
categories.push({
|
||||
id: photoCategoryRecord["id"],
|
||||
title: photoCategoryRecord["title"],
|
||||
url: photoCategoryRecord["url"],
|
||||
thumbnail: {
|
||||
url: photoCategoryRecord["thumbnail"]["filename_disk"],
|
||||
width: imageSize.width,
|
||||
height: imageSize.height
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
export async function getPhotoCategory(url: string): Promise<PhotoAlbumCategory> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getCategory), {
|
||||
url: url
|
||||
});
|
||||
|
||||
const item = result["Photo_Categories"][0];
|
||||
|
||||
const imageSize =
|
||||
getImageSize(item["thumbnail"]["width"], item["thumbnail"]["height"], 1.5);
|
||||
|
||||
let categories: PhotoAlbumCategory = {
|
||||
id: item["id"],
|
||||
title: item["title"],
|
||||
url: item["url"],
|
||||
thumbnail: {
|
||||
url: item["thumbnail"]["filename_disk"],
|
||||
width: imageSize.width,
|
||||
height: imageSize.height
|
||||
}
|
||||
};
|
||||
|
||||
return categories;
|
||||
}
|
||||
|
||||
45
astro/src/content/photos/photos.ts
Normal file
45
astro/src/content/photos/photos.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { createDirectusConnection } from "@/lib/directus";
|
||||
import { print } from "graphql";
|
||||
import getPhotos from '@/graphql/photos/getPhotos.graphql';
|
||||
import md5 from "md5";
|
||||
|
||||
export async function getPhotoFromHash(albumUrl: string, hash: string): Promise<PhotoAlbumItem | null> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getPhotos), {
|
||||
albumUrl: albumUrl
|
||||
});
|
||||
|
||||
let object: PhotoAlbumItem | null = null;
|
||||
|
||||
result["Photo_Albums"][0]["photos"].forEach((photo: any) => {
|
||||
/*
|
||||
* I have decided to not put the getImageSize here, it can mess up the
|
||||
* hashing, or anything else. It seems smarter to do this in the photo's and galleries.
|
||||
*/
|
||||
|
||||
const hashObject = md5(JSON.stringify({
|
||||
id: photo.id,
|
||||
url: photo.photo.filename_disk,
|
||||
width: photo.photo.width,
|
||||
height: photo.photo.height
|
||||
}));
|
||||
|
||||
if (hashObject.substring(hashObject.length - 10) === hash) {
|
||||
object = {
|
||||
id: photo.id,
|
||||
text: photo.text,
|
||||
photo: {
|
||||
url: photo.photo.filename_disk,
|
||||
width: photo.photo.width,
|
||||
height: photo.photo.height
|
||||
},
|
||||
album: {
|
||||
url: result["Photo_Albums"][0].url,
|
||||
title: result["Photo_Albums"][0].title
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return object;
|
||||
}
|
||||
301
astro/src/content/projects/projects.ts
Normal file
301
astro/src/content/projects/projects.ts
Normal file
@@ -0,0 +1,301 @@
|
||||
import { formatDate } from "@/lib/dates";
|
||||
import { createDirectusConnection } from "@/lib/directus";
|
||||
import { print } from "graphql";
|
||||
import getProjects from '@/graphql/projects/getProjects.graphql';
|
||||
import getProjectPost from '@/graphql/projects/getProject.graphql';
|
||||
import getLastProjectsQuery from '@/graphql/projects/getLastProjects.graphql';
|
||||
import { getImageSize } from "@/lib/images";
|
||||
|
||||
export async function getAllProjects(settings: GlobalSettings): Promise<ProjectPost[]> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getProjects), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D")
|
||||
});
|
||||
|
||||
let projects: ProjectPost[] = [];
|
||||
|
||||
result["Projects"].forEach((projectRecord: any) => {
|
||||
let dates: string[] = [
|
||||
settings.project.lastModified.toISOString(),
|
||||
settings.website.lastModified.toISOString(),
|
||||
projectRecord["date_created"],
|
||||
projectRecord["date_updated"],
|
||||
projectRecord["search_engine"][0]["date_created"],
|
||||
projectRecord["search_engine"][0]["date_updated"],
|
||||
projectRecord["search_engine"][0]["thumbnail"]["created_on"]
|
||||
];
|
||||
|
||||
const projectThumbnailImage =
|
||||
getImageSize(projectRecord["search_engine"][0]["thumbnail"]["width"], projectRecord["search_engine"][0]["thumbnail"]["height"], 0.756)
|
||||
|
||||
const project: ProjectPost = {
|
||||
exists: true,
|
||||
type: "ProjectPost",
|
||||
lastModified: new Date(),
|
||||
id: projectRecord["id"],
|
||||
title: projectRecord["title"],
|
||||
content: projectRecord["content"],
|
||||
date: projectRecord["date"],
|
||||
url: projectRecord["url"],
|
||||
searchEngine: {
|
||||
title: projectRecord["search_engine"][0]["title"],
|
||||
description: projectRecord["search_engine"][0]["description"],
|
||||
allowCrawlers: projectRecord["search_engine"][0]["allow_crawler"],
|
||||
canonical: projectRecord["search_engine"][0]["canonical"],
|
||||
priority: projectRecord["search_engine"][0]["priority"],
|
||||
thumbnail: {
|
||||
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"],
|
||||
width: projectThumbnailImage.width,
|
||||
height: projectThumbnailImage.height
|
||||
}
|
||||
},
|
||||
tags: []
|
||||
};
|
||||
|
||||
projectRecord["tags"].forEach((tagRecord: any) => {
|
||||
project["tags"].push({
|
||||
text: tagRecord["Tags_id"]["text"],
|
||||
code: tagRecord["Tags_id"]["code"],
|
||||
color: tagRecord["Tags_id"]["color"]
|
||||
});
|
||||
|
||||
dates.push(tagRecord["Tags_id"]["date_created"]);
|
||||
dates.push(tagRecord["Tags_id"]["date_updated"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
project.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
project.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
projects.push(project);
|
||||
});
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
export async function getProject(settings: GlobalSettings, route: string): Promise<ProjectPost> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getProjectPost), {
|
||||
route: route
|
||||
});
|
||||
|
||||
const projectRecord = result["Projects"][0];
|
||||
|
||||
let dates: string[] = [
|
||||
settings.project.lastModified.toISOString(),
|
||||
settings.website.lastModified.toISOString(),
|
||||
projectRecord["date_created"],
|
||||
projectRecord["date_updated"],
|
||||
projectRecord["search_engine"][0]["date_created"],
|
||||
projectRecord["search_engine"][0]["date_updated"],
|
||||
projectRecord["search_engine"][0]["thumbnail"]["created_on"]
|
||||
];
|
||||
|
||||
const projectThumbnailImage =
|
||||
getImageSize(projectRecord["search_engine"][0]["thumbnail"]["width"], projectRecord["search_engine"][0]["thumbnail"]["height"], 0.756)
|
||||
|
||||
const project: ProjectPost = {
|
||||
type: "ProjectPost",
|
||||
exists: true,
|
||||
|
||||
lastModified: new Date(),
|
||||
id: projectRecord["id"],
|
||||
title: projectRecord["title"],
|
||||
content: projectRecord["content"],
|
||||
date: projectRecord["date"],
|
||||
url: projectRecord["url"],
|
||||
searchEngine: {
|
||||
title: projectRecord["search_engine"][0]["title"],
|
||||
description: projectRecord["search_engine"][0]["description"],
|
||||
allowCrawlers: projectRecord["search_engine"][0]["allow_crawler"],
|
||||
canonical: projectRecord["search_engine"][0]["canonical"],
|
||||
priority: projectRecord["search_engine"][0]["priority"],
|
||||
thumbnail: {
|
||||
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"],
|
||||
width: projectThumbnailImage.width,
|
||||
height: projectThumbnailImage.height
|
||||
}
|
||||
},
|
||||
tags: []
|
||||
};
|
||||
|
||||
projectRecord["tags"].forEach((tagRecord: any) => {
|
||||
project["tags"].push({
|
||||
text: tagRecord["Tags_id"]["text"],
|
||||
code: tagRecord["Tags_id"]["code"],
|
||||
color: tagRecord["Tags_id"]["color"]
|
||||
});
|
||||
|
||||
dates.push(tagRecord["Tags_id"]["date_created"]);
|
||||
dates.push(tagRecord["Tags_id"]["date_updated"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
project.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
project.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
export async function getLastProjects(amount: number): Promise<ProjectPost[]> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getLastProjectsQuery), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D"),
|
||||
amount: amount
|
||||
});
|
||||
|
||||
let projects: ProjectPost[] = [];
|
||||
|
||||
result["Projects"].forEach((projectRecord: any) => {
|
||||
let dates: string[] = [
|
||||
projectRecord["date_created"],
|
||||
projectRecord["date_updated"],
|
||||
projectRecord["search_engine"][0]["date_created"],
|
||||
projectRecord["search_engine"][0]["date_updated"],
|
||||
projectRecord["search_engine"][0]["thumbnail"]["created_on"]
|
||||
];
|
||||
|
||||
const projectThumbnailImage =
|
||||
getImageSize(projectRecord["search_engine"][0]["thumbnail"]["width"], projectRecord["search_engine"][0]["thumbnail"]["height"], 0.756)
|
||||
|
||||
const project: ProjectPost = {
|
||||
exists: true,
|
||||
type: "ProjectPost",
|
||||
lastModified: new Date(),
|
||||
id: projectRecord["id"],
|
||||
title: projectRecord["title"],
|
||||
content: projectRecord["content"],
|
||||
date: projectRecord["date"],
|
||||
url: projectRecord["url"],
|
||||
searchEngine: {
|
||||
title: projectRecord["search_engine"][0]["title"],
|
||||
description: projectRecord["search_engine"][0]["description"],
|
||||
allowCrawlers: projectRecord["search_engine"][0]["allow_crawler"],
|
||||
canonical: projectRecord["search_engine"][0]["canonical"],
|
||||
priority: projectRecord["search_engine"][0]["priority"],
|
||||
thumbnail: {
|
||||
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"],
|
||||
width: projectThumbnailImage.width,
|
||||
height: projectThumbnailImage.height
|
||||
}
|
||||
},
|
||||
tags: []
|
||||
};
|
||||
|
||||
projectRecord["tags"].forEach((tagRecord: any) => {
|
||||
project["tags"].push({
|
||||
text: tagRecord["Tags_id"]["text"],
|
||||
code: tagRecord["Tags_id"]["code"],
|
||||
color: tagRecord["Tags_id"]["color"]
|
||||
});
|
||||
|
||||
dates.push(tagRecord["Tags_id"]["date_created"]);
|
||||
dates.push(tagRecord["Tags_id"]["date_updated"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
project.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
project.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
projects.push(project);
|
||||
});
|
||||
|
||||
return projects;
|
||||
}
|
||||
|
||||
export async function getAllPaginatedProjects(settings: GlobalSettings, page: number): Promise<ProjectPost[]> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getProjects), {
|
||||
date: formatDate(new Date(), "%Y-%M-%D"),
|
||||
limit: 6,
|
||||
pageNumber: page
|
||||
});
|
||||
|
||||
let projects: ProjectPost[] = [];
|
||||
|
||||
result["Projects"].forEach((projectRecord: any) => {
|
||||
let dates: string[] = [
|
||||
settings.project.lastModified.toISOString(),
|
||||
settings.website.lastModified.toISOString(),
|
||||
projectRecord["date_created"],
|
||||
projectRecord["date_updated"],
|
||||
projectRecord["search_engine"][0]["date_created"],
|
||||
projectRecord["search_engine"][0]["date_updated"],
|
||||
projectRecord["search_engine"][0]["thumbnail"]["created_on"]
|
||||
];
|
||||
|
||||
const projectThumbnailImage =
|
||||
getImageSize(projectRecord["search_engine"][0]["thumbnail"]["width"], projectRecord["search_engine"][0]["thumbnail"]["height"], 0.756)
|
||||
|
||||
const project: ProjectPost = {
|
||||
exists: true,
|
||||
type: "ProjectPost",
|
||||
lastModified: new Date(),
|
||||
id: projectRecord["id"],
|
||||
title: projectRecord["title"],
|
||||
content: projectRecord["content"],
|
||||
date: projectRecord["date"],
|
||||
url: projectRecord["url"],
|
||||
searchEngine: {
|
||||
title: projectRecord["search_engine"][0]["title"],
|
||||
description: projectRecord["search_engine"][0]["description"],
|
||||
allowCrawlers: projectRecord["search_engine"][0]["allow_crawler"],
|
||||
canonical: projectRecord["search_engine"][0]["canonical"],
|
||||
priority: projectRecord["search_engine"][0]["priority"],
|
||||
thumbnail: {
|
||||
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"],
|
||||
width: projectThumbnailImage.width,
|
||||
height: projectThumbnailImage.height
|
||||
}
|
||||
},
|
||||
tags: []
|
||||
};
|
||||
|
||||
projectRecord["tags"].forEach((tagRecord: any) => {
|
||||
project["tags"].push({
|
||||
text: tagRecord["Tags_id"]["text"],
|
||||
code: tagRecord["Tags_id"]["code"],
|
||||
color: tagRecord["Tags_id"]["color"]
|
||||
});
|
||||
|
||||
dates.push(tagRecord["Tags_id"]["date_created"]);
|
||||
dates.push(tagRecord["Tags_id"]["date_updated"]);
|
||||
});
|
||||
|
||||
if (dates.filter(e => e !== null).length === 0) {
|
||||
project.lastModified = new Date();
|
||||
}
|
||||
else {
|
||||
const sortedDates: string[] = dates.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
project.lastModified = new Date(sortedDates[0]);
|
||||
}
|
||||
|
||||
projects.push(project);
|
||||
});
|
||||
|
||||
return projects;
|
||||
}
|
||||
15
astro/src/content/settings/robots.ts
Normal file
15
astro/src/content/settings/robots.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { createDirectusConnection } from "@/lib/directus";
|
||||
import { print } from 'graphql';
|
||||
import getRobotsQuery from '@/graphql/settings/robots.graphql';
|
||||
|
||||
export async function getRobotsSettings(): Promise<RobotsSettings> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getRobotsQuery));
|
||||
|
||||
const robotsResult = result["Robots"];
|
||||
|
||||
return {
|
||||
crawlers: robotsResult["crawlers"],
|
||||
extraContent: robotsResult["extra_content"]
|
||||
};
|
||||
}
|
||||
169
astro/src/content/settings/settings.ts
Normal file
169
astro/src/content/settings/settings.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { print } from 'graphql';
|
||||
import { createDirectusConnection } from "@/lib/directus";
|
||||
import getSettingsQuery from '@/graphql/settings/settings.graphql';
|
||||
|
||||
export async function getSettings(): Promise<GlobalSettings> {
|
||||
const client = await createDirectusConnection();
|
||||
const result = await client.query(print(getSettingsQuery));
|
||||
|
||||
const websiteResults = result["Website_Settings"];
|
||||
const websiteSettings: WebsiteSettings = {
|
||||
domainName: websiteResults["domain_name"],
|
||||
titleTemplate: websiteResults["title_template"],
|
||||
applicationName: websiteResults["application_name"],
|
||||
colors: {
|
||||
primary: websiteResults["primary_color"],
|
||||
secondary: websiteResults["secondary_color"]
|
||||
},
|
||||
author: {
|
||||
name: websiteResults["author_name"],
|
||||
url: websiteResults["author_url"]
|
||||
},
|
||||
owner: websiteResults["owner"],
|
||||
designer: websiteResults["designer"],
|
||||
developer: websiteResults["developer"],
|
||||
copyright: websiteResults["copyright"],
|
||||
twitter: {
|
||||
id: websiteResults["twitter_id"],
|
||||
handle: websiteResults["twitter_handle"]
|
||||
},
|
||||
lastModified: websiteResults["date_updated"] !== null ?
|
||||
new Date(websiteResults["date_updated"]) :
|
||||
new Date(websiteResults["date_created"])
|
||||
};
|
||||
|
||||
const blogResults = result["Blog_Settings"];
|
||||
const blogSettings: BlogSettings = {
|
||||
enabled: blogResults["enabled"],
|
||||
title: blogResults["title"],
|
||||
subtext: blogResults["subtext"],
|
||||
indexRouteTemplate: blogResults["index_route_template"],
|
||||
blogRouteTemplate: blogResults["blog_route_template"],
|
||||
lastModified: blogResults["date_updated"] !== null ?
|
||||
new Date(blogResults["date_updated"]) :
|
||||
new Date(blogResults["date_created"])
|
||||
};
|
||||
|
||||
const projectResults = result["Project_Settings"];
|
||||
const projectSettings: ProjectSettings = {
|
||||
enabled: projectResults["enabled"],
|
||||
title: projectResults["title"],
|
||||
subtext: projectResults["subtext"],
|
||||
indexRouteTemplate: projectResults["index_route_template"],
|
||||
projectRouteTemplate: projectResults["project_route_template"],
|
||||
lastModified: projectResults["date_updated"] !== null ?
|
||||
new Date(projectResults["date_updated"]) :
|
||||
new Date(projectResults["date_created"])
|
||||
};
|
||||
|
||||
const photoResults = result["Photo_Settings"];
|
||||
let photoResultsLastModifiedTimestamps: string[] = [
|
||||
photoResults["date_created"],
|
||||
photoResults["date_updated"],
|
||||
photoResults["category_icons"]["date_created"],
|
||||
photoResults["category_icons"]["date_updated"],
|
||||
photoResults["category_icons"]["photos_icon"]["created_on"],
|
||||
photoResults["category_icons"]["location_icon"]["created_on"],
|
||||
photoResults["category_icons"]["date_icon"]["created_on"],
|
||||
photoResults["photo_icons"]["date_created"],
|
||||
photoResults["photo_icons"]["date_updated"],
|
||||
photoResults["photo_icons"]["previous_icon"]["created_on"],
|
||||
photoResults["photo_icons"]["next_icon"]["created_on"],
|
||||
photoResults["photo_icons"]["close_icon"]["created_on"],
|
||||
photoResults["photo_icons"]["download_icon"]["created_on"]
|
||||
];
|
||||
|
||||
const photoResultsLastModified = photoResultsLastModifiedTimestamps.sort((a: string, b: string) => {
|
||||
return new Date(b).getTime() - new Date(a).getTime();
|
||||
});
|
||||
|
||||
const photoSettings: WebsitePhotoSettings = {
|
||||
enabled: photoResults["enabled"],
|
||||
categoryIndex: {
|
||||
indexRouteTemplate: photoResults["categories_index_route_template_url"]
|
||||
},
|
||||
category: {
|
||||
routeTemplate: photoResults["category_route_template_url"],
|
||||
perPage: photoResults["albums_per_category_page"],
|
||||
icons: {
|
||||
photos: {
|
||||
url: photoResults["category_icons"]["photos_icon"]["filename_download"],
|
||||
height: photoResults["category_icons"]["photos_icon"]["height"],
|
||||
width: photoResults["category_icons"]["photos_icon"]["width"]
|
||||
},
|
||||
location: {
|
||||
url: photoResults["category_icons"]["location_icon"]["filename_download"],
|
||||
height: photoResults["category_icons"]["location_icon"]["height"],
|
||||
width: photoResults["category_icons"]["location_icon"]["width"]
|
||||
},
|
||||
date: {
|
||||
url: photoResults["category_icons"]["date_icon"]["filename_download"],
|
||||
height: photoResults["category_icons"]["date_icon"]["height"],
|
||||
width: photoResults["category_icons"]["date_icon"]["width"]
|
||||
}
|
||||
}
|
||||
},
|
||||
album: {
|
||||
routeTemplate: photoResults["album_route_template_url"],
|
||||
perPage: photoResults["photos_per_album_page"]
|
||||
},
|
||||
photo: {
|
||||
routeTemplate: photoResults["photo_route_template_url"],
|
||||
icons: {
|
||||
previous: {
|
||||
url: photoResults["photo_icons"]["previous_icon"]["filename_download"],
|
||||
height: photoResults["photo_icons"]["previous_icon"]["height"],
|
||||
width: photoResults["photo_icons"]["previous_icon"]["width"]
|
||||
},
|
||||
next: {
|
||||
url: photoResults["photo_icons"]["next_icon"]["filename_download"],
|
||||
height: photoResults["photo_icons"]["next_icon"]["height"],
|
||||
width: photoResults["photo_icons"]["next_icon"]["width"]
|
||||
},
|
||||
close: {
|
||||
url: photoResults["photo_icons"]["close_icon"]["filename_download"],
|
||||
height: photoResults["photo_icons"]["close_icon"]["height"],
|
||||
width: photoResults["photo_icons"]["close_icon"]["width"]
|
||||
},
|
||||
download: {
|
||||
url: photoResults["photo_icons"]["download_icon"]["filename_download"],
|
||||
height: photoResults["photo_icons"]["download_icon"]["height"],
|
||||
width: photoResults["photo_icons"]["download_icon"]["width"]
|
||||
}
|
||||
}
|
||||
},
|
||||
lastModified: new Date(photoResultsLastModified[0])
|
||||
};
|
||||
|
||||
const sitemapResults = result["Sitemap_Settings"];
|
||||
const sitemapSettings: SitemapSettings = {
|
||||
perPage: sitemapResults["per_page"],
|
||||
lastModified: sitemapResults["date_updated"] !== null ?
|
||||
new Date(sitemapResults["date_updated"]) :
|
||||
new Date(sitemapResults["date_created"])
|
||||
};
|
||||
|
||||
const pluginResults = result["Plugin_Settings"];
|
||||
const pluginSettings: PluginSettings = {
|
||||
swetrix: {
|
||||
id: pluginResults["swetrix_id"],
|
||||
url: pluginResults["swetrix_url"]
|
||||
},
|
||||
lastModified: pluginResults["date_updated"] !== null ?
|
||||
new Date(pluginResults["date_updated"]) :
|
||||
new Date(pluginResults["date_created"])
|
||||
}
|
||||
|
||||
if (pluginResults["swetrix_id"] === null && pluginResults["swetrix_url"] === null) {
|
||||
pluginSettings.swetrix = null;
|
||||
}
|
||||
|
||||
return {
|
||||
website: websiteSettings,
|
||||
blog: blogSettings,
|
||||
project: projectSettings,
|
||||
photo: photoSettings,
|
||||
sitemap: sitemapSettings,
|
||||
plugins: pluginSettings
|
||||
}
|
||||
}
|
||||
8
astro/src/env.d.ts
vendored
Normal file
8
astro/src/env.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
interface ImportMetaEnv {
|
||||
readonly DIRECTUS_TOKEN: string;
|
||||
readonly DIRECTUS_URL: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
5
astro/src/graphql.d.ts
vendored
Normal file
5
astro/src/graphql.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module '*.graphql' {
|
||||
import { DocumentNode } from 'graphql';
|
||||
const Schema: DocumentNode;
|
||||
export default Schema;
|
||||
}
|
||||
39
astro/src/graphql/blogs/getBlog.graphql
Normal file
39
astro/src/graphql/blogs/getBlog.graphql
Normal file
@@ -0,0 +1,39 @@
|
||||
query getAllBlogs($route: String!) {
|
||||
Blogs(sort: ["-date", "-date_created"], filter: { status: { _eq: "published" }, url: { _eq: $route } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
title,
|
||||
url,
|
||||
date,
|
||||
content,
|
||||
tags {
|
||||
Tags_id {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
code,
|
||||
color
|
||||
}
|
||||
},
|
||||
search_engine {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
canonical,
|
||||
allow_crawler,
|
||||
priority
|
||||
}
|
||||
}
|
||||
}
|
||||
39
astro/src/graphql/blogs/getBlogs.graphql
Normal file
39
astro/src/graphql/blogs/getBlogs.graphql
Normal file
@@ -0,0 +1,39 @@
|
||||
query getAllBlogs($date: String!) {
|
||||
Blogs(sort: ["-date", "-date_created"], filter: { status: { _eq: "published" }, date: { _lte: $date } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
title,
|
||||
url,
|
||||
date,
|
||||
content,
|
||||
tags {
|
||||
Tags_id {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
code,
|
||||
color
|
||||
}
|
||||
},
|
||||
search_engine {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
canonical,
|
||||
allow_crawler,
|
||||
priority
|
||||
}
|
||||
}
|
||||
}
|
||||
39
astro/src/graphql/blogs/getLastBlogPosts.graphql
Normal file
39
astro/src/graphql/blogs/getLastBlogPosts.graphql
Normal file
@@ -0,0 +1,39 @@
|
||||
query getLastBlogPosts($date: String!, $amount: Int!) {
|
||||
Blogs(sort: ["-date", "-date_created"], filter: { status: { _eq: "published" }, date: { _lte: $date } }, limit: $amount) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
title,
|
||||
url,
|
||||
date,
|
||||
content,
|
||||
tags {
|
||||
Tags_id {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
code,
|
||||
color
|
||||
}
|
||||
},
|
||||
search_engine {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
canonical,
|
||||
allow_crawler,
|
||||
priority
|
||||
}
|
||||
}
|
||||
}
|
||||
39
astro/src/graphql/blogs/getPaginatedBlogs.graphql
Normal file
39
astro/src/graphql/blogs/getPaginatedBlogs.graphql
Normal file
@@ -0,0 +1,39 @@
|
||||
query getPaginatedBlogs($date: String!, $pageNumber: Int!, $limit: Int!) {
|
||||
Blogs(sort: ["-date", "-date_created"], filter: { status: { _eq: "published" }, date: { _lte: $date } }, limit: $limit, page: $pageNumber) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
title,
|
||||
url,
|
||||
date,
|
||||
content,
|
||||
tags {
|
||||
Tags_id {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
code,
|
||||
color
|
||||
}
|
||||
},
|
||||
search_engine {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
canonical,
|
||||
allow_crawler,
|
||||
priority
|
||||
}
|
||||
}
|
||||
}
|
||||
50
astro/src/graphql/footer/getFooter.graphql
Normal file
50
astro/src/graphql/footer/getFooter.graphql
Normal file
@@ -0,0 +1,50 @@
|
||||
query getFooter {
|
||||
Footer {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
logo {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
copyright,
|
||||
columns {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
links {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
url
|
||||
}
|
||||
},
|
||||
socials {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
name,
|
||||
url,
|
||||
icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
},
|
||||
secondary_links {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
35
astro/src/graphql/menu/getMenu.graphql
Normal file
35
astro/src/graphql/menu/getMenu.graphql
Normal file
@@ -0,0 +1,35 @@
|
||||
query getMenu {
|
||||
Menu {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
items {
|
||||
id,
|
||||
item {
|
||||
...on Menu_Column {
|
||||
__typename,
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
links {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
url
|
||||
}
|
||||
}
|
||||
|
||||
...on Menu_Link {
|
||||
__typename,
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
224
astro/src/graphql/pages/getAllPages.graphql
Normal file
224
astro/src/graphql/pages/getAllPages.graphql
Normal file
@@ -0,0 +1,224 @@
|
||||
query getAllPages($date: String!) {
|
||||
Pages(filter: { status: { _eq: "published" } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
url,
|
||||
search_engine {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
canonical,
|
||||
allow_crawler,
|
||||
priority
|
||||
},
|
||||
components {
|
||||
id,
|
||||
__typename,
|
||||
item {
|
||||
__typename,
|
||||
|
||||
...on Hero {
|
||||
hero_id: id,
|
||||
hero_created: date_created,
|
||||
hero_updated: date_updated,
|
||||
hero_title: title,
|
||||
hero_text: subtext,
|
||||
background_image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
hero_image: background_image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
background_image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
...on Text_With_Side_Image {
|
||||
twsi_id: id,
|
||||
twsi_created: date_created,
|
||||
twsi_updated: date_updated,
|
||||
twsi_title: title,
|
||||
twsi_text: text,
|
||||
image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
twsi_image_side: image_side,
|
||||
twsi_image_size: image_size
|
||||
}
|
||||
|
||||
...on Wall_Of_Text {
|
||||
wot_id: id,
|
||||
wot_created: date_created,
|
||||
wot_updated: date_updated,
|
||||
wot_title: title,
|
||||
wot_text: text
|
||||
}
|
||||
|
||||
...on Frequently_Asked_Questions {
|
||||
faq_id: id,
|
||||
faq_created: date_created,
|
||||
faq_updated: date_updated,
|
||||
faq_title: title,
|
||||
faq_text: text,
|
||||
questions {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
question,
|
||||
answer
|
||||
}
|
||||
}
|
||||
|
||||
...on Upcoming_Events {
|
||||
ue_id: id,
|
||||
ue_created: date_created,
|
||||
ue_updated: date_updated,
|
||||
ue_title: title,
|
||||
ue_text: text,
|
||||
events(filter: { start_date: { _gte: $date } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
location,
|
||||
map_location,
|
||||
start_date,
|
||||
end_date,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
...on Equipment_Table {
|
||||
et_id: id,
|
||||
et_created: date_created,
|
||||
et_updated: date_updated,
|
||||
et_title: title,
|
||||
et_text: text,
|
||||
items {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
text,
|
||||
icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
...on Review_List {
|
||||
rl_id: id,
|
||||
rl_created: date_created,
|
||||
rl_updated: date_updated,
|
||||
rl_title: title,
|
||||
rl_text: text,
|
||||
reviews(sort: ["date"], filter: { status: { _eq: "published" } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
name,
|
||||
review,
|
||||
date,
|
||||
stars,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
...on Contact {
|
||||
c_id: id,
|
||||
c_created: date_created,
|
||||
c_updated: date_updated,
|
||||
c_title: title,
|
||||
c_text: text,
|
||||
methods {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
url,
|
||||
icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
...on Last_Blogs {
|
||||
lb_id: id,
|
||||
lb_created: date_created,
|
||||
lb_updated: date_updated,
|
||||
lb_title: title,
|
||||
lb_read_more_button_text: read_more_button_text,
|
||||
lb_amount: amount
|
||||
}
|
||||
|
||||
...on Last_Projects {
|
||||
lp_id: id,
|
||||
lp_created: date_created,
|
||||
lp_updated: date_updated,
|
||||
lp_title: title,
|
||||
lp_read_more_button_text: read_more_button_text,
|
||||
lp_amount: amount
|
||||
}
|
||||
|
||||
...on Last_Galleries {
|
||||
lg_id: id,
|
||||
lg_created: date_created,
|
||||
lg_updated: date_updated,
|
||||
lg_title: title,
|
||||
lg_read_more_button_text: read_more_button_text,
|
||||
lg_amount: amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
224
astro/src/graphql/pages/getPage.graphql
Normal file
224
astro/src/graphql/pages/getPage.graphql
Normal file
@@ -0,0 +1,224 @@
|
||||
query getAllPages($date: String!, $route: String!) {
|
||||
Pages(filter: { status: { _eq: "published" }, url: { _eq: $route } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
url,
|
||||
search_engine {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
canonical,
|
||||
allow_crawler,
|
||||
priority
|
||||
},
|
||||
components {
|
||||
id,
|
||||
__typename,
|
||||
item {
|
||||
__typename,
|
||||
|
||||
...on Hero {
|
||||
hero_id: id,
|
||||
hero_created: date_created,
|
||||
hero_updated: date_updated,
|
||||
hero_title: title,
|
||||
hero_text: subtext,
|
||||
background_image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
hero_image: background_image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
background_image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
...on Text_With_Side_Image {
|
||||
twsi_id: id,
|
||||
twsi_created: date_created,
|
||||
twsi_updated: date_updated,
|
||||
twsi_title: title,
|
||||
twsi_text: text,
|
||||
image {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
twsi_image_side: image_side,
|
||||
twsi_image_size: image_size
|
||||
}
|
||||
|
||||
...on Wall_Of_Text {
|
||||
wot_id: id,
|
||||
wot_created: date_created,
|
||||
wot_updated: date_updated,
|
||||
wot_title: title,
|
||||
wot_text: text
|
||||
}
|
||||
|
||||
...on Frequently_Asked_Questions {
|
||||
faq_id: id,
|
||||
faq_created: date_created,
|
||||
faq_updated: date_updated,
|
||||
faq_title: title,
|
||||
faq_text: text,
|
||||
questions {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
question,
|
||||
answer
|
||||
}
|
||||
}
|
||||
|
||||
...on Upcoming_Events {
|
||||
ue_id: id,
|
||||
ue_created: date_created,
|
||||
ue_updated: date_updated,
|
||||
ue_title: title,
|
||||
ue_text: text,
|
||||
events(filter: { start_date: { _gte: $date } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
location,
|
||||
map_location,
|
||||
start_date,
|
||||
end_date,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
...on Equipment_Table {
|
||||
et_id: id,
|
||||
et_created: date_created,
|
||||
et_updated: date_updated,
|
||||
et_title: title,
|
||||
et_text: text,
|
||||
items {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
text,
|
||||
icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
...on Review_List {
|
||||
rl_id: id,
|
||||
rl_created: date_created,
|
||||
rl_updated: date_updated,
|
||||
rl_title: title,
|
||||
rl_text: text,
|
||||
reviews(sort: ["date"], filter: { status: { _eq: "published" } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
name,
|
||||
review,
|
||||
date,
|
||||
stars,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
...on Contact {
|
||||
c_id: id,
|
||||
c_created: date_created,
|
||||
c_updated: date_updated,
|
||||
c_title: title,
|
||||
c_text: text,
|
||||
methods {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
url,
|
||||
icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
...on Last_Blogs {
|
||||
lb_id: id,
|
||||
lb_created: date_created,
|
||||
lb_updated: date_updated,
|
||||
lb_title: title,
|
||||
lb_read_more_button_text: read_more_button_text,
|
||||
lb_amount: amount
|
||||
}
|
||||
|
||||
...on Last_Projects {
|
||||
lp_id: id,
|
||||
lp_created: date_created,
|
||||
lp_updated: date_updated,
|
||||
lp_title: title,
|
||||
lp_read_more_button_text: read_more_button_text,
|
||||
lp_amount: amount
|
||||
}
|
||||
|
||||
...on Last_Galleries {
|
||||
lg_id: id,
|
||||
lg_created: date_created,
|
||||
lg_updated: date_updated,
|
||||
lg_title: title,
|
||||
lg_read_more_button_text: read_more_button_text,
|
||||
lg_amount: amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
astro/src/graphql/photos/getAlbum.graphql
Normal file
51
astro/src/graphql/photos/getAlbum.graphql
Normal file
@@ -0,0 +1,51 @@
|
||||
query getAllAlbums($route: String!) {
|
||||
Photo_Albums(sort: ["-start_date", "-date_created"], filter: { status: { _eq: "published" }, url: { _eq: $route }, category: { Photo_Categories_id: { status: { _eq: "published" } } } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
start_date,
|
||||
end_date,
|
||||
location,
|
||||
category {
|
||||
Photo_Categories_id {
|
||||
id,
|
||||
status,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
},
|
||||
photos(filter: { status: { _eq: "published" } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
photo {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
text,
|
||||
sort
|
||||
}
|
||||
}
|
||||
}
|
||||
51
astro/src/graphql/photos/getAlbums.graphql
Normal file
51
astro/src/graphql/photos/getAlbums.graphql
Normal file
@@ -0,0 +1,51 @@
|
||||
query getAllAlbums($date: String!) {
|
||||
Photo_Albums(sort: ["-start_date", "-date_created"], filter: { status: { _eq: "published" }, start_date: { _lte: $date }, category: { Photo_Categories_id: { status: { _eq: "published" } } } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
start_date,
|
||||
end_date,
|
||||
location,
|
||||
category {
|
||||
Photo_Categories_id {
|
||||
id,
|
||||
status,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
},
|
||||
photos(filter: { status: { _eq: "published" } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
photo {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
text,
|
||||
sort
|
||||
}
|
||||
}
|
||||
}
|
||||
17
astro/src/graphql/photos/getCategories.graphql
Normal file
17
astro/src/graphql/photos/getCategories.graphql
Normal file
@@ -0,0 +1,17 @@
|
||||
query getAllCategories {
|
||||
Photo_Categories(filter: { status: { _eq: "published" } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
title,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
17
astro/src/graphql/photos/getCategory.graphql
Normal file
17
astro/src/graphql/photos/getCategory.graphql
Normal file
@@ -0,0 +1,17 @@
|
||||
query getAllCategories($url: String!) {
|
||||
Photo_Categories(filter: { status: { _eq: "published" }, url: { _eq: $url } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
title,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
}
|
||||
51
astro/src/graphql/photos/getCategoryAlbum.graphql
Normal file
51
astro/src/graphql/photos/getCategoryAlbum.graphql
Normal file
@@ -0,0 +1,51 @@
|
||||
query getCategoryAlbums($date: String!, $categoryUrl: String!) {
|
||||
Photo_Albums(sort: ["-start_date", "-date_created"], filter: { status: { _eq: "published" }, start_date: { _lte: $date }, category: { Photo_Categories_id: { status: { _eq: "published" }, url: { _eq: $categoryUrl } } } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
start_date,
|
||||
end_date,
|
||||
location,
|
||||
category {
|
||||
Photo_Categories_id {
|
||||
id,
|
||||
status,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
},
|
||||
photos(filter: { status: { _eq: "published" } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
photo {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
text,
|
||||
sort
|
||||
}
|
||||
}
|
||||
}
|
||||
51
astro/src/graphql/photos/getLastAlbums.graphql
Normal file
51
astro/src/graphql/photos/getLastAlbums.graphql
Normal file
@@ -0,0 +1,51 @@
|
||||
query getLastAlbums($date: String!, $limit: Int!) {
|
||||
Photo_Albums(sort: ["-start_date", "-date_created"], filter: { status: { _eq: "published" }, start_date: { _lte: $date }, category: { Photo_Categories_id: { status: { _eq: "published" } } } }, limit: $limit) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
start_date,
|
||||
end_date,
|
||||
location,
|
||||
category {
|
||||
Photo_Categories_id {
|
||||
id,
|
||||
status,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
},
|
||||
photos(filter: { status: { _eq: "published" } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
photo {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
text,
|
||||
sort
|
||||
}
|
||||
}
|
||||
}
|
||||
51
astro/src/graphql/photos/getPhotos.graphql
Normal file
51
astro/src/graphql/photos/getPhotos.graphql
Normal file
@@ -0,0 +1,51 @@
|
||||
query getPhotos($albumUrl: String!) {
|
||||
Photo_Albums(sort: ["-start_date", "-date_created"], filter: { status: { _eq: "published" }, url: { _eq: $albumUrl }, category: { Photo_Categories_id: { status: { _eq: "published" } } } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
start_date,
|
||||
end_date,
|
||||
location,
|
||||
category {
|
||||
Photo_Categories_id {
|
||||
id,
|
||||
status,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
url,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
},
|
||||
photos(filter: { status: { _eq: "published" } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
photo {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
text,
|
||||
sort
|
||||
}
|
||||
}
|
||||
}
|
||||
39
astro/src/graphql/projects/getLastProjects.graphql
Normal file
39
astro/src/graphql/projects/getLastProjects.graphql
Normal file
@@ -0,0 +1,39 @@
|
||||
query getLastProjects($date: String!, $amount: Int!) {
|
||||
Projects(sort: ["-date", "-date_created"], filter: { status: { _eq: "published" }, date: { _lte: $date } }, limit: $amount) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
title,
|
||||
url,
|
||||
date,
|
||||
content,
|
||||
tags {
|
||||
Tags_id {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
code,
|
||||
color
|
||||
}
|
||||
},
|
||||
search_engine {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
canonical,
|
||||
allow_crawler,
|
||||
priority
|
||||
}
|
||||
}
|
||||
}
|
||||
39
astro/src/graphql/projects/getPaginatedProjects.graphql
Normal file
39
astro/src/graphql/projects/getPaginatedProjects.graphql
Normal file
@@ -0,0 +1,39 @@
|
||||
query getAllProjects($date: String!, $pageNumber: Int!, $limit: Int!) {
|
||||
Projects(sort: ["-date", "-date_created"], filter: { status: { _eq: "published" }, date: { _lte: $date } }, limit: $limit, page: $pageNumber) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
title,
|
||||
url,
|
||||
date,
|
||||
content,
|
||||
tags {
|
||||
Tags_id {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
code,
|
||||
color
|
||||
}
|
||||
},
|
||||
search_engine {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
canonical,
|
||||
allow_crawler,
|
||||
priority
|
||||
}
|
||||
}
|
||||
}
|
||||
39
astro/src/graphql/projects/getProject.graphql
Normal file
39
astro/src/graphql/projects/getProject.graphql
Normal file
@@ -0,0 +1,39 @@
|
||||
query getAllProjects($route: String!) {
|
||||
Projects(sort: ["-date", "-date_created"], filter: { status: { _eq: "published" }, url: { _eq: $route } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
title,
|
||||
url,
|
||||
date,
|
||||
content,
|
||||
tags {
|
||||
Tags_id {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
code,
|
||||
color
|
||||
}
|
||||
},
|
||||
search_engine {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
canonical,
|
||||
allow_crawler,
|
||||
priority
|
||||
}
|
||||
}
|
||||
}
|
||||
39
astro/src/graphql/projects/getProjects.graphql
Normal file
39
astro/src/graphql/projects/getProjects.graphql
Normal file
@@ -0,0 +1,39 @@
|
||||
query getAllProjects($date: String!) {
|
||||
Projects(sort: ["-date", "-date_created"], filter: { status: { _eq: "published" }, date: { _lte: $date } }) {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
status,
|
||||
title,
|
||||
url,
|
||||
date,
|
||||
content,
|
||||
tags {
|
||||
Tags_id {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
text,
|
||||
code,
|
||||
color
|
||||
}
|
||||
},
|
||||
search_engine {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
title,
|
||||
description,
|
||||
thumbnail {
|
||||
id,
|
||||
created_on,
|
||||
filename_disk,
|
||||
width,
|
||||
height
|
||||
},
|
||||
canonical,
|
||||
allow_crawler,
|
||||
priority
|
||||
}
|
||||
}
|
||||
}
|
||||
9
astro/src/graphql/settings/robots.graphql
Normal file
9
astro/src/graphql/settings/robots.graphql
Normal file
@@ -0,0 +1,9 @@
|
||||
query Robots {
|
||||
Robots {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
crawlers,
|
||||
extra_content
|
||||
}
|
||||
}
|
||||
124
astro/src/graphql/settings/settings.graphql
Normal file
124
astro/src/graphql/settings/settings.graphql
Normal file
@@ -0,0 +1,124 @@
|
||||
query getAllSettings {
|
||||
Website_Settings {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
domain_name,
|
||||
title_template,
|
||||
application_name,
|
||||
primary_color,
|
||||
secondary_color,
|
||||
author_name,
|
||||
author_url,
|
||||
designer,
|
||||
developer,
|
||||
owner,
|
||||
copyright,
|
||||
twitter_id,
|
||||
twitter_handle
|
||||
},
|
||||
Blog_Settings {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
enabled,
|
||||
title,
|
||||
subtext,
|
||||
index_route_template,
|
||||
blog_route_template
|
||||
},
|
||||
Project_Settings {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
enabled,
|
||||
title,
|
||||
subtext,
|
||||
index_route_template,
|
||||
project_route_template
|
||||
},
|
||||
Photo_Settings {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
enabled,
|
||||
categories_index_route_template_url,
|
||||
category_route_template_url,
|
||||
albums_per_category_page,
|
||||
category_icons {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
photos_icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_download,
|
||||
width,
|
||||
height
|
||||
},
|
||||
location_icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_download,
|
||||
width,
|
||||
height
|
||||
},
|
||||
date_icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_download,
|
||||
width,
|
||||
height
|
||||
}
|
||||
},
|
||||
album_route_template_url,
|
||||
photos_per_album_page,
|
||||
photo_route_template_url,
|
||||
photo_icons {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
previous_icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_download,
|
||||
width,
|
||||
height
|
||||
},
|
||||
next_icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_download,
|
||||
width,
|
||||
height
|
||||
},
|
||||
close_icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_download,
|
||||
width,
|
||||
height
|
||||
},
|
||||
download_icon {
|
||||
id,
|
||||
created_on,
|
||||
filename_download,
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
},
|
||||
Sitemap_Settings {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
per_page
|
||||
},
|
||||
Plugin_Settings {
|
||||
id,
|
||||
date_created,
|
||||
date_updated,
|
||||
swetrix_id,
|
||||
swetrix_url
|
||||
}
|
||||
}
|
||||
8
astro/src/icons/CalendarIcon.astro
Normal file
8
astro/src/icons/CalendarIcon.astro
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
interface Props {
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
---
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={Astro.props.width ?? "24"} height={Astro.props.height ?? "24"} viewBox="0 0 512 512"><rect width="416" height="384" x="48" y="80" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="48"/><circle cx="296" cy="232" r="24" fill="currentColor"/><circle cx="376" cy="232" r="24" fill="currentColor"/><circle cx="296" cy="312" r="24" fill="currentColor"/><circle cx="376" cy="312" r="24" fill="currentColor"/><circle cx="136" cy="312" r="24" fill="currentColor"/><circle cx="216" cy="312" r="24" fill="currentColor"/><circle cx="136" cy="392" r="24" fill="currentColor"/><circle cx="216" cy="392" r="24" fill="currentColor"/><circle cx="296" cy="392" r="24" fill="currentColor"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M128 48v32m256-32v32"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" d="M464 160H48"/></svg>
|
||||
10
astro/src/icons/ChevronUp.astro
Normal file
10
astro/src/icons/ChevronUp.astro
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
interface Props {
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
---
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={Astro.props.width ?? "24"} height={Astro.props.height ?? "24"} viewBox="0 0 24 24">
|
||||
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m4 15l8-8l8 8" />
|
||||
</svg>
|
||||
10
astro/src/icons/Close.astro
Normal file
10
astro/src/icons/Close.astro
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
interface Props {
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
---
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={Astro.props.width ?? "24"} height={Astro.props.height ?? "24"} viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M6.4 19L5 17.6l5.6-5.6L5 6.4L6.4 5l5.6 5.6L17.6 5L19 6.4L13.4 12l5.6 5.6l-1.4 1.4l-5.6-5.6z" />
|
||||
</svg>
|
||||
10
astro/src/icons/Download.astro
Normal file
10
astro/src/icons/Download.astro
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
interface Props {
|
||||
width?: number;
|
||||
height?: number;
|
||||
}
|
||||
---
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={Astro.props.width ?? "24"} height={Astro.props.height ?? "24"} viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-6 4q-.825 0-1.412-.587T4 18v-3h2v3h12v-3h2v3q0 .825-.587 1.413T18 20z" />
|
||||
</svg>
|
||||
5
astro/src/icons/jsx/calendarIcon.tsx
Normal file
5
astro/src/icons/jsx/calendarIcon.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
export function CalendarIcon(props: { width?: number, height?: number }) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={props.width ?? "24"} height={props.height ?? "24"} viewBox="0 0 512 512"><rect width="416" height="384" x="48" y="80" fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" rx="48"/><circle cx="296" cy="232" r="24" fill="currentColor"/><circle cx="376" cy="232" r="24" fill="currentColor"/><circle cx="296" cy="312" r="24" fill="currentColor"/><circle cx="376" cy="312" r="24" fill="currentColor"/><circle cx="136" cy="312" r="24" fill="currentColor"/><circle cx="216" cy="312" r="24" fill="currentColor"/><circle cx="136" cy="392" r="24" fill="currentColor"/><circle cx="216" cy="392" r="24" fill="currentColor"/><circle cx="296" cy="392" r="24" fill="currentColor"/><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32" d="M128 48v32m256-32v32"/><path fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="32" d="M464 160H48"/></svg>
|
||||
)
|
||||
}
|
||||
5
astro/src/icons/jsx/loadingSpinner.tsx
Normal file
5
astro/src/icons/jsx/loadingSpinner.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
export function LoadingSpinner(props: { width?: number, height?: number }) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width={props.width ?? "24"} height={props.height ?? "24"} viewBox="0 0 24 24"><circle cx="12" cy="2" r="0" fill="currentColor"><animate attributeName="r" begin="0" calcMode="spline" dur="1s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite" values="0;2;0;0"/></circle><circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(45 12 12)"><animate attributeName="r" begin="0.125s" calcMode="spline" dur="1s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite" values="0;2;0;0"/></circle><circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(90 12 12)"><animate attributeName="r" begin="0.25s" calcMode="spline" dur="1s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite" values="0;2;0;0"/></circle><circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(135 12 12)"><animate attributeName="r" begin="0.375s" calcMode="spline" dur="1s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite" values="0;2;0;0"/></circle><circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(180 12 12)"><animate attributeName="r" begin="0.5s" calcMode="spline" dur="1s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite" values="0;2;0;0"/></circle><circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(225 12 12)"><animate attributeName="r" begin="0.625s" calcMode="spline" dur="1s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite" values="0;2;0;0"/></circle><circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(270 12 12)"><animate attributeName="r" begin="0.75s" calcMode="spline" dur="1s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite" values="0;2;0;0"/></circle><circle cx="12" cy="2" r="0" fill="currentColor" transform="rotate(315 12 12)"><animate attributeName="r" begin="0.875s" calcMode="spline" dur="1s" keySplines="0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8;0.2 0.2 0.4 0.8" repeatCount="indefinite" values="0;2;0;0"/></circle></svg>
|
||||
)
|
||||
}
|
||||
87
astro/src/layouts/BlogLayout.astro
Normal file
87
astro/src/layouts/BlogLayout.astro
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
import '@/styles/global.css';
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import { getTextColor } from '@/lib/colors';
|
||||
import Footer from '@/components/footer/Footer.astro';
|
||||
|
||||
interface Props {
|
||||
settings: BlogLayoutProps;
|
||||
}
|
||||
|
||||
const pageSettings = Astro.props.settings.searchEngine;
|
||||
const settings = await getSettings();
|
||||
|
||||
const css = {
|
||||
"--ptc": settings.website.colors.primary,
|
||||
"--stc": settings.website.colors.secondary ?? settings.website.colors.primary,
|
||||
"--ptt": getTextColor(settings.website.colors.primary),
|
||||
"--stt": settings.website.colors.secondary
|
||||
? getTextColor(settings.website.colors.secondary)
|
||||
: getTextColor(settings.website.colors.primary)
|
||||
};
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- High Priority Metadata -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="lanuage" content="en" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="theme-color" content={settings.website.colors.primary} />
|
||||
|
||||
<!-- High Priority Page Metadata -->
|
||||
<title>{settings.website.titleTemplate.replaceAll("%T", pageSettings.title)}</title>
|
||||
|
||||
<!-- Medium Priority Metadata -->
|
||||
<meta name="msapplication-TileColor" content={settings.website.colors.primary} />
|
||||
<meta name="msapplication-TileImage" content="" />
|
||||
<link rel="sitemap" href="/sitemap/index.xml" />
|
||||
<link rel="alternate" type="application/rss+xml" href="/rss.xml" title="RSS" />
|
||||
<link rel="canonical" href={`${settings.website.domainName}/`} />
|
||||
<meta name="robots" content="index,follow" />
|
||||
<meta name="keywords" content={[].join(',')} />
|
||||
|
||||
<!-- Low Priority Page Metadata -->
|
||||
<meta name="description" content={pageSettings.description} />
|
||||
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:locale" content="en-GB" />
|
||||
<meta property="og:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} />
|
||||
<meta property="og:description" content={pageSettings.description} />
|
||||
<meta property="og:image:url" content={pageSettings.thumbnail.url} />
|
||||
<meta property="og:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
|
||||
<meta property="og:site_name" content={settings.website.applicationName} />
|
||||
<meta property="article:author" content={settings.website.author.name} />
|
||||
<meta property="article:tags" content={[].join(',')} />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} />
|
||||
<meta name="twitter:description" content={pageSettings.description} />
|
||||
<meta name="twitter:image" content={pageSettings.thumbnail.url} />
|
||||
<meta name="twitter:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
|
||||
<meta name="twitter:site" content={settings.website.twitter.handle} />
|
||||
<meta name="twitter:creator" content={settings.website.twitter.handle} />
|
||||
|
||||
<meta name="pagename" content={pageSettings.title} />
|
||||
<meta name="category" content="webpage" />
|
||||
|
||||
<!-- Low Priority Metadata -->
|
||||
<meta name="copyright" content={settings.website.copyright} />
|
||||
<meta name="author" content={`${settings.website.author.name}, ${settings.website.author.url}`} />
|
||||
<meta name="designer" content={settings.website.designer} />
|
||||
<meta name="owner" content={settings.website.owner} />
|
||||
<meta name="developer" content={settings.website.developer} />
|
||||
<meta name="application-name" content={settings.website.applicationName} />
|
||||
|
||||
<!-- Scripts and Style -->
|
||||
</head>
|
||||
|
||||
<body style={ css } class="bg-[#fcfcfc] flex flex-col min-h-screen">
|
||||
<slot class="grow" name="content" />
|
||||
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
82
astro/src/layouts/PhotoLayout.astro
Normal file
82
astro/src/layouts/PhotoLayout.astro
Normal file
@@ -0,0 +1,82 @@
|
||||
---
|
||||
import '@/styles/global.css';
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import { getTextColor } from '@/lib/colors';
|
||||
import Footer from '@/components/footer/Footer.astro';
|
||||
|
||||
interface Props {
|
||||
settings: WebpageLayoutProps;
|
||||
}
|
||||
|
||||
const pageSettings = Astro.props.settings.searchEngine;
|
||||
const settings = await getSettings();
|
||||
|
||||
const css = {
|
||||
"--ptc": settings.website.colors.primary,
|
||||
"--stc": settings.website.colors.secondary ?? settings.website.colors.primary,
|
||||
"--ptt": getTextColor(settings.website.colors.primary),
|
||||
"--stt": settings.website.colors.secondary
|
||||
? getTextColor(settings.website.colors.secondary)
|
||||
: getTextColor(settings.website.colors.primary)
|
||||
};
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- High Priority Metadata -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="lanuage" content="en" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="theme-color" content={settings.website.colors.primary} />
|
||||
|
||||
<!-- High Priority Page Metadata -->
|
||||
<title>{settings.website.titleTemplate.replaceAll("%T", pageSettings.title)}</title>
|
||||
|
||||
<!-- Medium Priority Metadata -->
|
||||
<meta name="msapplication-TileColor" content={settings.website.colors.primary} />
|
||||
<meta name="msapplication-TileImage" content="" />
|
||||
<link rel="sitemap" href="/sitemap/index.xml" />
|
||||
<link rel="alternate" type="application/rss+xml" href="/rss.xml" title="RSS" />
|
||||
<link rel="canonical" href={`${settings.website.domainName}/`} />
|
||||
<meta name="robots" content="index,follow" />
|
||||
|
||||
<!-- Low Priority Page Metadata -->
|
||||
<meta name="description" content={pageSettings.description} />
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:locale" content="en-GB" />
|
||||
<meta property="og:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} />
|
||||
<meta property="og:description" content={pageSettings.description} />
|
||||
<meta property="og:image:url" content={pageSettings.thumbnail.url} />
|
||||
<meta property="og:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
|
||||
<meta property="og:site_name" content={settings.website.applicationName} />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} />
|
||||
<meta name="twitter:description" content={pageSettings.description} />
|
||||
<meta name="twitter:image" content={pageSettings.thumbnail.url} />
|
||||
<meta name="twitter:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
|
||||
<meta name="twitter:site" content={settings.website.twitter.handle} />
|
||||
<meta name="twitter:creator" content={settings.website.twitter.handle} />
|
||||
|
||||
<meta name="pagename" content={pageSettings.title} />
|
||||
<meta name="category" content="webpage" />
|
||||
|
||||
<!-- Low Priority Metadata -->
|
||||
<meta name="copyright" content={settings.website.copyright} />
|
||||
<meta name="author" content={`${settings.website.author.name}, ${settings.website.author.url}`} />
|
||||
<meta name="designer" content={settings.website.designer} />
|
||||
<meta name="owner" content={settings.website.owner} />
|
||||
<meta name="developer" content={settings.website.developer} />
|
||||
<meta name="application-name" content={settings.website.applicationName} />
|
||||
|
||||
<!-- Scripts and Style -->
|
||||
</head>
|
||||
|
||||
<body style={ css } class="bg-neutral-950 flex flex-col min-h-screen">
|
||||
<slot class="grow" name="content" />
|
||||
</body>
|
||||
</html>
|
||||
87
astro/src/layouts/ProjectLayout.astro
Normal file
87
astro/src/layouts/ProjectLayout.astro
Normal file
@@ -0,0 +1,87 @@
|
||||
---
|
||||
import '@/styles/global.css';
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import { getTextColor } from '@/lib/colors';
|
||||
import Footer from '@/components/footer/Footer.astro';
|
||||
|
||||
interface Props {
|
||||
settings: BlogLayoutProps;
|
||||
}
|
||||
|
||||
const pageSettings = Astro.props.settings.searchEngine;
|
||||
const settings = await getSettings();
|
||||
|
||||
const css = {
|
||||
"--ptc": settings.website.colors.primary,
|
||||
"--stc": settings.website.colors.secondary ?? settings.website.colors.primary,
|
||||
"--ptt": getTextColor(settings.website.colors.primary),
|
||||
"--stt": settings.website.colors.secondary
|
||||
? getTextColor(settings.website.colors.secondary)
|
||||
: getTextColor(settings.website.colors.primary)
|
||||
};
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- High Priority Metadata -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="lanuage" content="en" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="theme-color" content={settings.website.colors.primary} />
|
||||
|
||||
<!-- High Priority Page Metadata -->
|
||||
<title>{settings.website.titleTemplate.replaceAll("%T", pageSettings.title)}</title>
|
||||
|
||||
<!-- Medium Priority Metadata -->
|
||||
<meta name="msapplication-TileColor" content={settings.website.colors.primary} />
|
||||
<meta name="msapplication-TileImage" content="" />
|
||||
<link rel="sitemap" href="/sitemap/index.xml" />
|
||||
<link rel="alternate" type="application/rss+xml" href="/rss.xml" title="RSS" />
|
||||
<link rel="canonical" href={`${settings.website.domainName}/`} />
|
||||
<meta name="robots" content="index,follow" />
|
||||
<meta name="keywords" content={[].join(',')} />
|
||||
|
||||
<!-- Low Priority Page Metadata -->
|
||||
<meta name="description" content={pageSettings.description} />
|
||||
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:locale" content="en-GB" />
|
||||
<meta property="og:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} />
|
||||
<meta property="og:description" content={pageSettings.description} />
|
||||
<meta property="og:image:url" content={pageSettings.thumbnail.url} />
|
||||
<meta property="og:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
|
||||
<meta property="og:site_name" content={settings.website.applicationName} />
|
||||
<meta property="article:author" content={settings.website.author.name} />
|
||||
<meta property="article:tags" content={[].join(',')} />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} />
|
||||
<meta name="twitter:description" content={pageSettings.description} />
|
||||
<meta name="twitter:image" content={pageSettings.thumbnail.url} />
|
||||
<meta name="twitter:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
|
||||
<meta name="twitter:site" content={settings.website.twitter.handle} />
|
||||
<meta name="twitter:creator" content={settings.website.twitter.handle} />
|
||||
|
||||
<meta name="pagename" content={pageSettings.title} />
|
||||
<meta name="category" content="webpage" />
|
||||
|
||||
<!-- Low Priority Metadata -->
|
||||
<meta name="copyright" content={settings.website.copyright} />
|
||||
<meta name="author" content={`${settings.website.author.name}, ${settings.website.author.url}`} />
|
||||
<meta name="designer" content={settings.website.designer} />
|
||||
<meta name="owner" content={settings.website.owner} />
|
||||
<meta name="developer" content={settings.website.developer} />
|
||||
<meta name="application-name" content={settings.website.applicationName} />
|
||||
|
||||
<!-- Scripts and Style -->
|
||||
</head>
|
||||
|
||||
<body style={ css } class="bg-[#fcfcfc] flex flex-col min-h-screen">
|
||||
<slot class="grow" name="content" />
|
||||
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
84
astro/src/layouts/WebpageLayout.astro
Normal file
84
astro/src/layouts/WebpageLayout.astro
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
import '@/styles/global.css';
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import { getTextColor } from '@/lib/colors';
|
||||
import Footer from '@/components/footer/Footer.astro';
|
||||
|
||||
interface Props {
|
||||
settings: WebpageLayoutProps;
|
||||
}
|
||||
|
||||
const pageSettings = Astro.props.settings.searchEngine;
|
||||
const settings = await getSettings();
|
||||
|
||||
const css = {
|
||||
"--ptc": settings.website.colors.primary,
|
||||
"--stc": settings.website.colors.secondary ?? settings.website.colors.primary,
|
||||
"--ptt": getTextColor(settings.website.colors.primary),
|
||||
"--stt": settings.website.colors.secondary
|
||||
? getTextColor(settings.website.colors.secondary)
|
||||
: getTextColor(settings.website.colors.primary)
|
||||
};
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- High Priority Metadata -->
|
||||
<meta charset="utf-8" />
|
||||
<meta name="lanuage" content="en" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="theme-color" content={settings.website.colors.primary} />
|
||||
|
||||
<!-- High Priority Page Metadata -->
|
||||
<title>{settings.website.titleTemplate.replaceAll("%T", pageSettings.title)}</title>
|
||||
|
||||
<!-- Medium Priority Metadata -->
|
||||
<meta name="msapplication-TileColor" content={settings.website.colors.primary} />
|
||||
<meta name="msapplication-TileImage" content="" />
|
||||
<link rel="sitemap" href="/sitemap/index.xml" />
|
||||
<link rel="alternate" type="application/rss+xml" href="/rss.xml" title="RSS" />
|
||||
<link rel="canonical" href={`${settings.website.domainName}/`} />
|
||||
<meta name="robots" content="index,follow" />
|
||||
|
||||
<!-- Low Priority Page Metadata -->
|
||||
<meta name="description" content={pageSettings.description} />
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:locale" content="en-GB" />
|
||||
<meta property="og:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} />
|
||||
<meta property="og:description" content={pageSettings.description} />
|
||||
<meta property="og:image:url" content={pageSettings.thumbnail.url} />
|
||||
<meta property="og:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
|
||||
<meta property="og:site_name" content={settings.website.applicationName} />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} />
|
||||
<meta name="twitter:description" content={pageSettings.description} />
|
||||
<meta name="twitter:image" content={pageSettings.thumbnail.url} />
|
||||
<meta name="twitter:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
|
||||
<meta name="twitter:site" content={settings.website.twitter.handle} />
|
||||
<meta name="twitter:creator" content={settings.website.twitter.handle} />
|
||||
|
||||
<meta name="pagename" content={pageSettings.title} />
|
||||
<meta name="category" content="webpage" />
|
||||
|
||||
<!-- Low Priority Metadata -->
|
||||
<meta name="copyright" content={settings.website.copyright} />
|
||||
<meta name="author" content={`${settings.website.author.name}, ${settings.website.author.url}`} />
|
||||
<meta name="designer" content={settings.website.designer} />
|
||||
<meta name="owner" content={settings.website.owner} />
|
||||
<meta name="developer" content={settings.website.developer} />
|
||||
<meta name="application-name" content={settings.website.applicationName} />
|
||||
|
||||
<!-- Scripts and Style -->
|
||||
</head>
|
||||
|
||||
<body style={ css } class="bg-[#fcfcfc] flex flex-col min-h-screen">
|
||||
<slot class="grow" name="content" />
|
||||
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
23
astro/src/lib/colors.ts
Normal file
23
astro/src/lib/colors.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export function getTextColor(bgColor: string) {
|
||||
// Remove # if present
|
||||
const hex = bgColor.replace('#', '');
|
||||
|
||||
// Convert hex to RGB
|
||||
const r = parseInt(hex.substring(0, 2), 16) / 255;
|
||||
const g = parseInt(hex.substring(2, 4), 16) / 255;
|
||||
const b = parseInt(hex.substring(4, 6), 16) / 255;
|
||||
|
||||
// Convert to linear RGB
|
||||
const toLinear = (c: any) =>
|
||||
c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
||||
|
||||
const R = toLinear(r);
|
||||
const G = toLinear(g);
|
||||
const B = toLinear(b);
|
||||
|
||||
// Calculate luminance
|
||||
const luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B;
|
||||
|
||||
// Return white for dark backgrounds, black for light backgrounds
|
||||
return luminance > 0.179 ? '#000000' : '#fcfcfc';
|
||||
}
|
||||
6
astro/src/lib/dates.ts
Normal file
6
astro/src/lib/dates.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export function formatDate(date: Date, format: string) {
|
||||
return format
|
||||
.replaceAll("%Y", date.getFullYear().toString())
|
||||
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0'))
|
||||
.replaceAll("%D", date.getDate().toString().padStart(2, '0'));
|
||||
}
|
||||
9
astro/src/lib/directus.ts
Normal file
9
astro/src/lib/directus.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createDirectus, graphql, staticToken } from "@directus/sdk";
|
||||
|
||||
export async function createDirectusConnection() {
|
||||
const directus = await createDirectus(import.meta.env.DIRECTUS_URL)
|
||||
.with(graphql())
|
||||
.with(staticToken(import.meta.env.DIRECTUS_TOKEN));
|
||||
|
||||
return directus;
|
||||
}
|
||||
12
astro/src/lib/hash.ts
Normal file
12
astro/src/lib/hash.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import md5 from "md5";
|
||||
|
||||
export function getPhotoHash(photo: PhotoAlbumPhoto) {
|
||||
const hash = md5(JSON.stringify({
|
||||
id: photo.id,
|
||||
url: photo.photo.url,
|
||||
width: photo.photo.width,
|
||||
height: photo.photo.height
|
||||
}));
|
||||
|
||||
return hash.substring(hash.length - 10);
|
||||
}
|
||||
22
astro/src/lib/images.ts
Normal file
22
astro/src/lib/images.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export function getImageUrl(url: string) {
|
||||
return `${import.meta.env.DIRECTUS_URL}assets/${url}`;
|
||||
}
|
||||
|
||||
export function getImageSize(width: number, height: number, targetMegapixels: number): ResizedImageResponse {
|
||||
const originalPixels = width * height;
|
||||
const targetPixels = targetMegapixels * 1000 * 1000;
|
||||
|
||||
if (originalPixels <= targetPixels) {
|
||||
return {
|
||||
width,
|
||||
height
|
||||
}
|
||||
}
|
||||
|
||||
const scale = Math.sqrt(targetPixels / originalPixels);
|
||||
|
||||
return {
|
||||
width: Math.round(scale * width),
|
||||
height: Math.round(scale * height)
|
||||
}
|
||||
}
|
||||
9
astro/src/lib/markdown.ts
Normal file
9
astro/src/lib/markdown.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import markdownit from "markdown-it";
|
||||
import markdownitHighlightjs from "markdown-it-highlightjs";
|
||||
|
||||
export function markdownToHtml(markdown: string) {
|
||||
const md = markdownit()
|
||||
.use(markdownitHighlightjs);
|
||||
|
||||
return md.render(markdown);
|
||||
}
|
||||
261
astro/src/lib/pages.ts
Normal file
261
astro/src/lib/pages.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import { getBlog } from "@/content/blogs/blogs";
|
||||
import { getWebpage } from "@/content/pages/pages";
|
||||
import { getAlbum } from "@/content/photos/albums";
|
||||
import { getAllCategories, getPhotoCategory } from "@/content/photos/categories";
|
||||
import { getPhotoFromHash } from "@/content/photos/photos";
|
||||
import { getProject } from "@/content/projects/projects";
|
||||
import { getImageSize } from "./images";
|
||||
import { getImage } from "astro:assets";
|
||||
|
||||
export async function getPage(settings: GlobalSettings, route: string): Promise<PageType | null> {
|
||||
// Blog Index
|
||||
if (regexToRoute({ template: settings.blog.indexRouteTemplate, allowPagination: true }).regex.test(route)) {
|
||||
const { regex, keys } = regexToRoute({ template: settings.blog.indexRouteTemplate, allowPagination: true });
|
||||
const match = route.match(regex);
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
keys.forEach((key, i) => {
|
||||
params[key] = match[i + 1];
|
||||
});
|
||||
|
||||
return {
|
||||
route: route,
|
||||
pageType: "BlogIndex",
|
||||
page: {
|
||||
type: "BlogIndex",
|
||||
exists: true,
|
||||
pageNumber: params["page"] !== undefined ? Number(params["page"]) : 1
|
||||
}
|
||||
};
|
||||
}
|
||||
// Blog Post
|
||||
else if (regexToRoute({ template: settings.blog.blogRouteTemplate, allowPagination: false }).regex.test(route)) {
|
||||
const { regex, keys } = regexToRoute({ template: settings.blog.blogRouteTemplate, allowPagination: false });
|
||||
const match = route.match(regex);
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
keys.forEach((key, i) => {
|
||||
params[key] = match[i + 1];
|
||||
});
|
||||
|
||||
const blog = await getBlog(settings, `/${params["R"]}`);
|
||||
|
||||
return {
|
||||
route: route,
|
||||
pageType: "BlogPost",
|
||||
page: blog
|
||||
};
|
||||
}
|
||||
// Project Index
|
||||
else if (regexToRoute({ template: settings.project.indexRouteTemplate, allowPagination: true }).regex.test(route)) {
|
||||
const { regex, keys } = regexToRoute({ template: settings.project.indexRouteTemplate, allowPagination: true });
|
||||
const match = route.match(regex);
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
keys.forEach((key, i) => {
|
||||
params[key] = match[i + 1];
|
||||
});
|
||||
|
||||
return {
|
||||
route: route,
|
||||
pageType: "ProjectIndex",
|
||||
page: {
|
||||
type: "ProjectIndex",
|
||||
exists: true,
|
||||
pageNumber: params["page"] !== undefined ? Number(params["page"]) : 1
|
||||
}
|
||||
};
|
||||
}
|
||||
// Project Post
|
||||
else if (regexToRoute({ template: settings.project.projectRouteTemplate, allowPagination: false }).regex.test(route)) {
|
||||
const { regex, keys } = regexToRoute({ template: settings.project.projectRouteTemplate, allowPagination: false });
|
||||
const match = route.match(regex);
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
keys.forEach((key, i) => {
|
||||
params[key] = match[i + 1];
|
||||
});
|
||||
|
||||
const project = await getProject(settings, `/${params["R"]}`);
|
||||
|
||||
return {
|
||||
route: route,
|
||||
pageType: "ProjectPost",
|
||||
page: project
|
||||
};
|
||||
}
|
||||
// Photo Category Index
|
||||
else if (regexToRoute({ template: settings.photo.categoryIndex.indexRouteTemplate, allowPagination: false }).regex.test(route)) {
|
||||
const allCategories = await getAllCategories(settings);
|
||||
const lastCategory = allCategories[0];
|
||||
|
||||
return {
|
||||
route: route,
|
||||
pageType: "PhotoCategoryIndex",
|
||||
page: {
|
||||
category: lastCategory,
|
||||
type: "PhotoCategoryIndex",
|
||||
exists: true
|
||||
}
|
||||
};
|
||||
}
|
||||
// Photo Category / Album List
|
||||
else if (regexToRoute({ template: settings.photo.category.routeTemplate, allowPagination: true }).regex.test(route)) {
|
||||
const { regex, keys } = regexToRoute({ template: settings.photo.category.routeTemplate, allowPagination: true });
|
||||
const match = route.match(regex);
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
keys.forEach((key, i) => {
|
||||
params[key] = match[i + 1];
|
||||
});
|
||||
|
||||
const category = await getPhotoCategory(`/${params["C"]}`);
|
||||
|
||||
return {
|
||||
route: route,
|
||||
pageType: "PhotoCategory",
|
||||
page: {
|
||||
type: "PhotoCategory",
|
||||
exists: true,
|
||||
category: category,
|
||||
pageNumber: params["page"] !== undefined ? Number(params["page"]) : 1
|
||||
}
|
||||
};
|
||||
}
|
||||
// Photo Album
|
||||
else if (regexToRoute({ template: settings.photo.album.routeTemplate, allowPagination: true }).regex.test(route)) {
|
||||
const { regex, keys } = regexToRoute({ template: settings.photo.album.routeTemplate, allowPagination: true });
|
||||
const match = route.match(regex);
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
keys.forEach((key, i) => {
|
||||
params[key] = match[i + 1];
|
||||
});
|
||||
|
||||
const photoAlbum = await getAlbum(settings, `/${params["R"]}`);
|
||||
|
||||
return {
|
||||
route: route,
|
||||
pageType: "PhotoAlbum",
|
||||
page: {
|
||||
...photoAlbum,
|
||||
pageNumber: params["page"] !== undefined ? Number(params['page']) : 1
|
||||
}
|
||||
};
|
||||
}
|
||||
// Photograph
|
||||
else if (regexToRoute({ template: settings.photo.photo.routeTemplate, allowPagination: false }).regex.test(route)) {
|
||||
const { regex, keys } = regexToRoute({ template: settings.photo.photo.routeTemplate, allowPagination: false });
|
||||
const match = route.match(regex);
|
||||
const params: Record<string, string> = {};
|
||||
|
||||
if (!match) return null;
|
||||
|
||||
keys.forEach((key, i) => {
|
||||
params[key] = match[i + 1];
|
||||
});
|
||||
|
||||
const photo = await getPhotoFromHash(`/${params["R"]}`, params["H"]);
|
||||
|
||||
if (photo === null) {}
|
||||
|
||||
return {
|
||||
route: route,
|
||||
pageType: "Photo",
|
||||
page: {
|
||||
type: "PhotoPage",
|
||||
exists: true,
|
||||
|
||||
id: photo!.id,
|
||||
photo: photo!.photo,
|
||||
text: photo!.text,
|
||||
album: photo!.album
|
||||
}
|
||||
};
|
||||
}
|
||||
// Regular webpage
|
||||
else if (regexToRoute({ template: "/", allowPagination: false }).regex.test(route) ||
|
||||
regexToRoute({ template: "/%R", allowPagination: false }).regex.test(route)) {
|
||||
const webpageContent = await getWebpage(route);
|
||||
|
||||
if (webpageContent === null || !webpageContent.exists) {
|
||||
return {
|
||||
route: route,
|
||||
pageType: "Webpage",
|
||||
page: {
|
||||
type: "Webpage",
|
||||
exists: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const resizedImage = getImageSize(webpageContent.searchEngine.thumbnail.width,
|
||||
webpageContent.searchEngine.thumbnail.height, 0.756);
|
||||
|
||||
const thumbnail = await getImage({
|
||||
src: webpageContent.searchEngine.thumbnail.url,
|
||||
width: resizedImage.width,
|
||||
height: resizedImage.height,
|
||||
format: "jpeg"
|
||||
});
|
||||
|
||||
return {
|
||||
route: route,
|
||||
pageType: "Webpage",
|
||||
page: {
|
||||
type: "Webpage",
|
||||
exists: true,
|
||||
id: webpageContent.id,
|
||||
lastModified: webpageContent.lastModified,
|
||||
url: webpageContent.url,
|
||||
searchEngine: {
|
||||
...webpageContent.searchEngine,
|
||||
thumbnail: {
|
||||
url: `${settings.website.domainName}${thumbnail.src}`,
|
||||
width: resizedImage.width,
|
||||
height: resizedImage.height
|
||||
}
|
||||
},
|
||||
components: webpageContent.components
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function regexToRoute(template: PageRegexMatchProps) {
|
||||
const keys: string[] = [];
|
||||
|
||||
let pattern = template.template
|
||||
.replaceAll("%Y", () => { keys.push("Y"); return "(\\d{4})"; })
|
||||
.replaceAll("%M", () => { keys.push("M"); return "(\\d{2})"; })
|
||||
.replaceAll("%D", () => { keys.push("D"); return "(\\d{2})"; })
|
||||
.replaceAll("%R", () => { keys.push("R"); return "([^/]+)"; })
|
||||
.replaceAll("%C", () => { keys.push("C"); return "([^/]+)"; })
|
||||
.replaceAll("%H", () => { keys.push("H"); return "([^/]+)"; })
|
||||
.replace(/\/+/g, "/");
|
||||
|
||||
if (template.allowPagination) {
|
||||
keys.push("page");
|
||||
pattern += "(?:\\/(\\d+))?";
|
||||
}
|
||||
|
||||
return {
|
||||
regex: new RegExp(`^${pattern}$`),
|
||||
keys
|
||||
};
|
||||
}
|
||||
148
astro/src/lib/routing.ts
Normal file
148
astro/src/lib/routing.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { getAllBlogs } from "@/content/blogs/blogs";
|
||||
import { getAllWebpages } from "@/content/pages/pages";
|
||||
import { getAllAlbums } from "@/content/photos/albums";
|
||||
import { getAllProjects } from "@/content/projects/projects";
|
||||
import { getPhotoHash } from "./hash";
|
||||
import { getAllCategories } from "@/content/photos/categories";
|
||||
|
||||
export async function getAllRoutesList(settings: GlobalSettings): Promise<string[]> {
|
||||
let routes: string[] = [];
|
||||
|
||||
const webpages = await getAllWebpages();
|
||||
|
||||
webpages.forEach((webpage) => {
|
||||
if (webpage.exists) {
|
||||
routes.push(webpage.url);
|
||||
}
|
||||
});
|
||||
|
||||
if (settings.blog.enabled) {
|
||||
const blogs = await getAllBlogs(settings);
|
||||
|
||||
for (let i = 0; i < Math.ceil(blogs.length / 6); i++) {
|
||||
if (i !== 0) {
|
||||
routes.push(`${settings.blog.indexRouteTemplate}/${i + 1}`);
|
||||
}
|
||||
else {
|
||||
routes.push(settings.blog.indexRouteTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
blogs.forEach((blog) => {
|
||||
routes.push(getBlogRoute(settings.blog, blog));
|
||||
});
|
||||
}
|
||||
if (settings.project.enabled) {
|
||||
const projects = await getAllProjects(settings);
|
||||
|
||||
for (let i = 0; i < Math.ceil(projects.length / 4); i++) {
|
||||
if (i !== 0) {
|
||||
routes.push(`${settings.project.indexRouteTemplate}/${i + 1}`);
|
||||
}
|
||||
else {
|
||||
routes.push(settings.project.indexRouteTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
projects.forEach((project) => {
|
||||
routes.push(getProjectRoute(settings.project, project));
|
||||
});
|
||||
}
|
||||
if (settings.photo.enabled) {
|
||||
const categories = await getAllCategories(settings);
|
||||
|
||||
if (categories.length > 0) {
|
||||
const galleries = await getAllAlbums(settings);
|
||||
|
||||
routes.push(settings.photo.categoryIndex.indexRouteTemplate);
|
||||
|
||||
categories.forEach((category) => {
|
||||
let albums = galleries.filter(g => g.category.id === category.id);
|
||||
const pages = Math.ceil(albums.length / settings.photo.category.perPage);
|
||||
const categoryRoute = getCategoryRoute(settings.photo, category);
|
||||
|
||||
for (let i = 0; i < pages; i++) {
|
||||
if (i !== 0) {
|
||||
routes.push(`${categoryRoute}/${i + 1}`);
|
||||
}
|
||||
else {
|
||||
routes.push(`${categoryRoute}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
galleries.forEach((gallery) => {
|
||||
const pages = Math.ceil(gallery.photos.length / settings.photo.album.perPage);
|
||||
const galleryRoute = getAlbumRoute(settings.photo, gallery);
|
||||
|
||||
for (let i = 0; i < pages; i++) {
|
||||
if (i !== 0) {
|
||||
routes.push(`${galleryRoute}/${i + 1}`);
|
||||
}
|
||||
else {
|
||||
routes.push(`${galleryRoute}`);
|
||||
}
|
||||
}
|
||||
|
||||
gallery.photos.forEach((photo) => {
|
||||
routes.push(getPhotoRoute(settings.photo, gallery, photo));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
export function getBlogRoute(blogSettings: BlogSettings, blog: BlogPost) {
|
||||
const date = new Date(blog.date);
|
||||
|
||||
return blogSettings.blogRouteTemplate
|
||||
.replaceAll("%Y", date.getFullYear().toString())
|
||||
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0'))
|
||||
.replaceAll("%D", date.getDate().toString().padStart(2, '0'))
|
||||
.replaceAll("%R", blog.url)
|
||||
.replace(/\/+/g, '/');
|
||||
}
|
||||
|
||||
export function getProjectRoute(projectSettings: ProjectSettings, project: ProjectPost) {
|
||||
const date = new Date(project.date);
|
||||
|
||||
return projectSettings.projectRouteTemplate
|
||||
.replaceAll("%Y", date.getFullYear().toString())
|
||||
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0'))
|
||||
.replaceAll("%D", date.getDate().toString().padStart(2, '0'))
|
||||
.replaceAll("%R", project.url)
|
||||
.replace(/\/+/g, '/');
|
||||
}
|
||||
|
||||
export function getCategoryRoute(photoSettings: WebsitePhotoSettings, category: PhotoAlbumCategory) {
|
||||
return photoSettings.category.routeTemplate
|
||||
.replaceAll("%C", category.url)
|
||||
.replace(/\/+/g, '/');
|
||||
}
|
||||
|
||||
export function getAlbumRoute(photoSettings: WebsitePhotoSettings, album: PhotoAlbum) {
|
||||
const date = new Date(album.startDate);
|
||||
|
||||
return photoSettings.album.routeTemplate
|
||||
.replaceAll("%Y", date.getFullYear().toString())
|
||||
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0'))
|
||||
.replaceAll("%D", date.getDate().toString().padStart(2, '0'))
|
||||
.replaceAll("%C", album.category.url)
|
||||
.replaceAll("%R", album.url)
|
||||
.replace(/\/+/g, '/');
|
||||
}
|
||||
|
||||
export function getPhotoRoute(photoSettings: WebsitePhotoSettings, album: PhotoAlbum, photo: PhotoAlbumPhoto) {
|
||||
const date = new Date(album.startDate);
|
||||
|
||||
return photoSettings.photo.routeTemplate
|
||||
.replaceAll("%Y", date.getFullYear().toString())
|
||||
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0'))
|
||||
.replaceAll("%D", date.getDate().toString().padStart(2, '0'))
|
||||
.replaceAll("%C", album.category.url)
|
||||
.replaceAll("%R", album.url)
|
||||
.replaceAll("%H", getPhotoHash(photo))
|
||||
.replace(/\/+/g, '/');
|
||||
}
|
||||
183
astro/src/pages/[...route].astro
Normal file
183
astro/src/pages/[...route].astro
Normal file
@@ -0,0 +1,183 @@
|
||||
---
|
||||
import { getAllRoutesList } from "@/lib/routing";
|
||||
import { getPage } from "@/lib/pages";
|
||||
import { getSettings } from "@/content/settings/settings"
|
||||
import WebpageLayout from "@/layouts/WebpageLayout.astro";
|
||||
import BlogLayout from "@/layouts/BlogLayout.astro";
|
||||
import ProjectLayout from "@/layouts/ProjectLayout.astro";
|
||||
import PhotoLayout from '@/layouts/PhotoLayout.astro';
|
||||
import BlogIndex from "@/components/blogs/BlogIndex.astro";
|
||||
import ProjectIndex from "@/components/projects/ProjectIndex.astro";
|
||||
import Webpage from "@/components/webpage/Webpage.astro";
|
||||
import BlogPost from "@/components/blogs/BlogPost.astro";
|
||||
import ProjectPost from "@/components/projects/ProjectPost.astro";
|
||||
import CategoryIndex from "@/components/photos/CategoryIndex.astro";
|
||||
import Category from "@/components/photos/Category.astro";
|
||||
import AlbumPage from "@/components/photos/Album.astro";
|
||||
import { getImageUrl } from "@/lib/images";
|
||||
import Photo from "@/components/photos/Photo.astro";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const settings = await getSettings();
|
||||
const pages = await getAllRoutesList(settings);
|
||||
|
||||
let routes: any[] = [];
|
||||
|
||||
pages.forEach((page) => {
|
||||
routes.push({ params: { route: page } });
|
||||
});
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
const settings = await getSettings();
|
||||
const pathName = Astro.url.pathname === "/" ? "/" : Astro.url.pathname.replace(/\/$/, "");
|
||||
const page = await getPage(settings, pathName);
|
||||
|
||||
if (page === null || page.page === null || !page.page.exists) {
|
||||
return new Response("Page not found.", {
|
||||
status: 404,
|
||||
statusText: "Not Found"
|
||||
});
|
||||
}
|
||||
---
|
||||
|
||||
{ page.page.type === "Webpage" && page.page.exists && (
|
||||
<WebpageLayout settings={{
|
||||
searchEngine: page.page.searchEngine
|
||||
}}>
|
||||
<Fragment slot="content">
|
||||
<Webpage webpage={page.page.components} />
|
||||
</Fragment>
|
||||
</WebpageLayout>
|
||||
) }
|
||||
|
||||
{ page.page.type === "BlogIndex" && (
|
||||
<WebpageLayout settings={{
|
||||
searchEngine: {
|
||||
title: "Blogs",
|
||||
description: "",
|
||||
allowCrawlers: true,
|
||||
canonical: null,
|
||||
priority: 65,
|
||||
thumbnail: {
|
||||
url: "",
|
||||
width: 1200,
|
||||
height: 630
|
||||
}
|
||||
}}}>
|
||||
<Fragment slot="content">
|
||||
<BlogIndex page={page.page} />
|
||||
</Fragment>
|
||||
</WebpageLayout>
|
||||
) }
|
||||
|
||||
{ page.page.type === "BlogPost" && (
|
||||
<BlogLayout settings={{
|
||||
searchEngine: page.page.searchEngine,
|
||||
tags: page.page.tags.map((tag) => tag.text)
|
||||
}}>
|
||||
<Fragment slot="content">
|
||||
<BlogPost blog={page.page} />
|
||||
</Fragment>
|
||||
</BlogLayout>
|
||||
) }
|
||||
|
||||
{ page.page.type === "ProjectIndex" && (
|
||||
<WebpageLayout settings={{
|
||||
searchEngine: {
|
||||
title: "Projects",
|
||||
description: "",
|
||||
allowCrawlers: true,
|
||||
canonical: null,
|
||||
priority: 65,
|
||||
thumbnail: {
|
||||
url: "",
|
||||
width: 1200,
|
||||
height: 630
|
||||
}
|
||||
}}}>
|
||||
<Fragment slot="content">
|
||||
<ProjectIndex page={page.page} />
|
||||
</Fragment>
|
||||
</WebpageLayout>
|
||||
) }
|
||||
|
||||
{ page.page.type === "ProjectPost" && (
|
||||
<ProjectLayout settings={{
|
||||
searchEngine: page.page.searchEngine,
|
||||
tags: page.page.tags.map((tag) => tag.text)
|
||||
}}>
|
||||
<Fragment slot="content">
|
||||
<ProjectPost project={page.page} />
|
||||
</Fragment>
|
||||
</ProjectLayout>
|
||||
) }
|
||||
|
||||
{ page.pageType === "PhotoCategoryIndex" && (
|
||||
<WebpageLayout settings={{
|
||||
searchEngine: {
|
||||
title: "Categories",
|
||||
description: "See the photo categories on this page, where you can see the different types of photography I do.",
|
||||
allowCrawlers: true,
|
||||
canonical: null,
|
||||
priority: 65,
|
||||
thumbnail: page.page.category.thumbnail
|
||||
}}}>
|
||||
<Fragment slot="content">
|
||||
<CategoryIndex />
|
||||
</Fragment>
|
||||
</WebpageLayout>
|
||||
) }
|
||||
|
||||
{ page.pageType === "PhotoCategory" && (
|
||||
<WebpageLayout settings={{
|
||||
searchEngine: {
|
||||
title: page.page.category.title,
|
||||
description: `See the photos in the ${page.page.category.title.toLowerCase()} category.`,
|
||||
allowCrawlers: true,
|
||||
canonical: null,
|
||||
priority: 65,
|
||||
thumbnail: page.page.category.thumbnail
|
||||
}}}>
|
||||
<Fragment slot="content">
|
||||
<Category category={page.page} />
|
||||
</Fragment>
|
||||
</WebpageLayout>
|
||||
) }
|
||||
|
||||
{ page.pageType === "PhotoAlbum" && (
|
||||
<WebpageLayout settings={{
|
||||
searchEngine: {
|
||||
title: page.page.title,
|
||||
description: `See the photos in the ${page.page.category.title.toLowerCase()} category.`,
|
||||
allowCrawlers: true,
|
||||
canonical: null,
|
||||
priority: 65,
|
||||
thumbnail: page.page.category.thumbnail
|
||||
}}}>
|
||||
<Fragment slot="content">
|
||||
<AlbumPage page={page.page} />
|
||||
</Fragment>
|
||||
</WebpageLayout>
|
||||
) }
|
||||
|
||||
{ page.pageType === "Photo" && (
|
||||
<PhotoLayout settings={{
|
||||
searchEngine: {
|
||||
title: page.page.album.title,
|
||||
description: `See this photo from the album ${page.page.album.title}`,
|
||||
allowCrawlers: true,
|
||||
canonical: null,
|
||||
priority: 65,
|
||||
thumbnail: {
|
||||
url: getImageUrl(page.page.photo.url),
|
||||
width: page.page.photo.width,
|
||||
height: page.page.photo.height
|
||||
}
|
||||
}}}>
|
||||
<Fragment slot="content">
|
||||
<Photo page={page.page} />
|
||||
</Fragment>
|
||||
</PhotoLayout>
|
||||
) }
|
||||
66
astro/src/pages/robots.txt.ts
Normal file
66
astro/src/pages/robots.txt.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { getRobotsSettings } from "@/content/settings/robots";
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import type { APIRoute } from "astro";
|
||||
|
||||
export const GET = (async () => {
|
||||
const settings = await getSettings();
|
||||
const robots = await getRobotsSettings();
|
||||
|
||||
let crawlers = [
|
||||
{ id: 'google', name: 'Googlebot' },
|
||||
{ id: 'bing', name: "Bingbot" },
|
||||
{ id: "slurp", name: "Slurp" },
|
||||
{ id: "duckduckgo", name: "DuckDuckBot" },
|
||||
{ id: "baidu", name: "Baiduspider" },
|
||||
{ id: "yandex", name: "YandexBot" },
|
||||
{ id: "sogou", name: "Sogou web spider" },
|
||||
{ id: "seznam", name: "SeznamBot" },
|
||||
{ id: "qwantbot", name: "Qwantbot" },
|
||||
{ id: "naverbot", name: "Naverbot" },
|
||||
{ id: "coccocbot", name: "Coccocbot" },
|
||||
{ id: "mojeekbot", name: "Mojeekbot" },
|
||||
{ id: "ahrefs", name: "Ahrefsbot" },
|
||||
{ id: "semrush", name: "SemrushBot" },
|
||||
{ id: "mj12bot", name: "MJ12Bot" },
|
||||
{ id: "dotbot", name: "DotBot" },
|
||||
{ id: "petalbot", name: "PetalBot" },
|
||||
{ id: "gptbot", name: "GPTBot" },
|
||||
{ id: "ccbot", name: "CCBot" },
|
||||
{ id: "ia_archiver", name: "ia_archiver" },
|
||||
{ id: "claudebot", name: "ClaudeBot" },
|
||||
{ id: "perplexity", name: "PerplexityBot" },
|
||||
{ id: "facebookexternalhit", name: "facebookexternalhit/1.1" },
|
||||
{ id: "twitterbot", name: "Twitterbot" },
|
||||
{ id: "linkedinbot", name: "LinkedInBot" },
|
||||
{ id: "bytespider", name: "ByteSpider" },
|
||||
{ id: "applebot", name: "AppleBot" },
|
||||
{ id: "amazonbot", name: "AmazonBot" }
|
||||
]
|
||||
|
||||
let crawlerContent = "";
|
||||
|
||||
crawlers.forEach((crawler) => {
|
||||
if (robots.crawlers.some(c => c === crawler.id)) {
|
||||
const crawlerData = crawlers.find(c => c.id === crawler.id);
|
||||
|
||||
crawlerContent = crawlerContent +
|
||||
`User-agent: ${crawlerData!.name}\nAllow: /\nCrawl-delay: 5\nSitemap: ${settings.website.domainName}/sitemap/index.xml`
|
||||
}
|
||||
else {
|
||||
const crawlerData = crawlers.find(c => c.id === crawler.id);
|
||||
|
||||
crawlerContent = crawlerContent +
|
||||
`User-agent: ${crawlerData!.name}\nDisallow: /`
|
||||
}
|
||||
|
||||
crawlerContent = crawlerContent + "\n\n"
|
||||
});
|
||||
|
||||
return new Response(crawlerContent.trim(), {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
"Content-Type": "text/plain"
|
||||
}
|
||||
});
|
||||
}) satisfies APIRoute;
|
||||
27
astro/src/pages/rss.xml.ts
Normal file
27
astro/src/pages/rss.xml.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import type { APIRoute } from "astro";
|
||||
import minifyXML from "minify-xml";
|
||||
|
||||
export const GET = (async () => {
|
||||
const settings = await getSettings();
|
||||
|
||||
let sitemapContent = `
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>${settings.website.applicationName}</title>
|
||||
<description>This is the RSS feed of ${settings.website.applicationName}</description>
|
||||
<link>${settings.website.domainName}</link>
|
||||
<lastBuildDate>Sat, 13 Dec 2003 18:30:02 GMT</lastBuildDate>
|
||||
</channel>
|
||||
</rss>
|
||||
`;
|
||||
|
||||
return new Response(minifyXML(sitemapContent), {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
"Content-Type": "application/xml"
|
||||
}
|
||||
});
|
||||
}) satisfies APIRoute;
|
||||
70
astro/src/pages/sitemap/albums-[page].xml.ts
Normal file
70
astro/src/pages/sitemap/albums-[page].xml.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { getAllAlbums } from "@/content/photos/albums";
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import { getAlbumRoute } from "@/lib/routing";
|
||||
import type { APIRoute } from "astro";
|
||||
import minifyXML from "minify-xml";
|
||||
|
||||
export const GET = (async ({ params }) => {
|
||||
const settings = await getSettings();
|
||||
|
||||
if (!settings.photo.enabled) {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
statusText: "Not Found"
|
||||
});
|
||||
}
|
||||
|
||||
const currentPage = params.page;
|
||||
|
||||
const albums = await getAllAlbums(settings);
|
||||
const selectedAlbums = albums.slice(
|
||||
((Number(currentPage) - 1) * settings.sitemap.perPage),
|
||||
Number(currentPage) * settings.sitemap.perPage - 1
|
||||
);
|
||||
|
||||
let pages: SitemapPage[] = [];
|
||||
|
||||
selectedAlbums.forEach((album) => {
|
||||
pages.push({
|
||||
url: getAlbumRoute(settings.photo, album),
|
||||
lastModified: album.lastModified
|
||||
});
|
||||
});
|
||||
|
||||
let sitemapContent = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${pages.map((page) => `
|
||||
<sitemap>
|
||||
<loc>${settings.website.domainName}${page.url}</loc>
|
||||
<lastmod>${page.lastModified.toISOString()}</lastmod>
|
||||
</sitemap>
|
||||
`).join('')}
|
||||
</sitemapindex>
|
||||
`;
|
||||
|
||||
return new Response(minifyXML(sitemapContent), {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
"Content-Type": "application/xml"
|
||||
}
|
||||
});
|
||||
}) satisfies APIRoute;
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const settings = await getSettings();
|
||||
const albums = await getAllAlbums(settings);
|
||||
|
||||
const albumCount = albums.length;
|
||||
const perPage = settings.sitemap.perPage;
|
||||
const pages = Math.ceil(albumCount / perPage);
|
||||
|
||||
let items: any[] = [];
|
||||
|
||||
for (let i = 0; i < pages; i++) {
|
||||
items.push({ params: { page: (i + 1).toString() } });
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
68
astro/src/pages/sitemap/albums.xml.ts
Normal file
68
astro/src/pages/sitemap/albums.xml.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { getAllAlbums } from "@/content/photos/albums";
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import type { APIRoute } from "astro";
|
||||
import minifyXML from "minify-xml";
|
||||
|
||||
export const GET = (async () => {
|
||||
const settings = await getSettings();
|
||||
|
||||
if (!settings.photo.enabled) {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
statusText: "Not Found"
|
||||
});
|
||||
}
|
||||
|
||||
const albums = await getAllAlbums(settings);
|
||||
const albumCount = albums.length;
|
||||
const perPage = settings.sitemap.perPage;
|
||||
const pages = Math.ceil(albumCount / perPage);
|
||||
|
||||
let sitemaps: SitemapIndex[] = [];
|
||||
|
||||
for (let i = 0; i < pages; i++) {
|
||||
const selectedProjects = albums.slice(
|
||||
((Number(i + 1) - 1) * settings.sitemap.perPage),
|
||||
Number(i + 1) * settings.sitemap.perPage - 1
|
||||
);
|
||||
|
||||
let dates = [
|
||||
settings.sitemap.lastModified,
|
||||
settings.photo.lastModified,
|
||||
settings.website.lastModified
|
||||
];
|
||||
|
||||
selectedProjects.forEach((project) => {
|
||||
dates.push(project.lastModified);
|
||||
});
|
||||
|
||||
const lastModified = dates.sort((a: Date, b: Date) => {
|
||||
return b.getTime() - a.getTime();
|
||||
});
|
||||
|
||||
sitemaps.push({
|
||||
url: `/sitemap/albums-${i + 1}.xml`,
|
||||
lastModified: lastModified[0]
|
||||
});
|
||||
}
|
||||
|
||||
let sitemapContent = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${sitemaps.map((item) => `
|
||||
<sitemap>
|
||||
<loc>${settings.website.domainName}${item.url}</loc>
|
||||
<lastmod>${item.lastModified.toISOString()}</lastmod>
|
||||
</sitemap>
|
||||
`).join('')}
|
||||
</sitemapindex>
|
||||
`;
|
||||
|
||||
return new Response(minifyXML(sitemapContent), {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
"Content-Type": "application/xml"
|
||||
}
|
||||
});
|
||||
}) satisfies APIRoute;
|
||||
70
astro/src/pages/sitemap/blogs-[page].xml.ts
Normal file
70
astro/src/pages/sitemap/blogs-[page].xml.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { getAllBlogs } from "@/content/blogs/blogs";
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import { getBlogRoute } from "@/lib/routing";
|
||||
import type { APIRoute } from "astro";
|
||||
import minifyXML from "minify-xml";
|
||||
|
||||
export const GET = (async ({ params }) => {
|
||||
const settings = await getSettings();
|
||||
|
||||
if (!settings.blog.enabled) {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
statusText: "Not Found"
|
||||
});
|
||||
}
|
||||
|
||||
const currentPage = params.page;
|
||||
|
||||
const blogs = await getAllBlogs(settings);
|
||||
const selectedBlogs = blogs.slice(
|
||||
((Number(currentPage) - 1) * settings.sitemap.perPage),
|
||||
Number(currentPage) * settings.sitemap.perPage - 1
|
||||
);
|
||||
|
||||
let pages: SitemapPage[] = [];
|
||||
|
||||
selectedBlogs.forEach((blog) => {
|
||||
pages.push({
|
||||
url: getBlogRoute(settings.blog, blog),
|
||||
lastModified: blog.lastModified
|
||||
});
|
||||
})
|
||||
|
||||
let sitemapContent = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${pages.map((page) => `
|
||||
<sitemap>
|
||||
<loc>${settings.website.domainName}${page.url}</loc>
|
||||
<lastmod>${page.lastModified.toISOString()}</lastmod>
|
||||
</sitemap>
|
||||
`).join('')}
|
||||
</sitemapindex>
|
||||
`;
|
||||
|
||||
return new Response(minifyXML(sitemapContent), {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
"Content-Type": "application/xml"
|
||||
}
|
||||
});
|
||||
}) satisfies APIRoute;
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const settings = await getSettings();
|
||||
const blogs = await getAllBlogs(settings);
|
||||
|
||||
const blogCount = blogs.length;
|
||||
const perPage = settings.sitemap.perPage;
|
||||
const pages = Math.ceil(blogCount / perPage);
|
||||
|
||||
let items: any[] = [];
|
||||
|
||||
for (let i = 0; i < pages; i++) {
|
||||
items.push({ params: { page: (i + 1).toString() } });
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
68
astro/src/pages/sitemap/blogs.xml.ts
Normal file
68
astro/src/pages/sitemap/blogs.xml.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { getAllBlogs } from "@/content/blogs/blogs";
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import type { APIRoute } from "astro";
|
||||
import minifyXML from "minify-xml";
|
||||
|
||||
export const GET = (async () => {
|
||||
const settings = await getSettings();
|
||||
|
||||
if (!settings.blog.enabled) {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
statusText: "Not Found"
|
||||
});
|
||||
}
|
||||
|
||||
const blogs = await getAllBlogs(settings);
|
||||
const blogCount = blogs.length;
|
||||
const perPage = settings.sitemap.perPage;
|
||||
const pages = Math.ceil(blogCount / perPage);
|
||||
|
||||
let sitemaps: SitemapIndex[] = [];
|
||||
|
||||
for (let i = 0; i < pages; i++) {
|
||||
const selectedBlogs = blogs.slice(
|
||||
((Number(i + 1) - 1) * settings.sitemap.perPage),
|
||||
Number(i + 1) * settings.sitemap.perPage - 1
|
||||
);
|
||||
|
||||
let dates = [
|
||||
settings.sitemap.lastModified,
|
||||
settings.blog.lastModified,
|
||||
settings.website.lastModified
|
||||
];
|
||||
|
||||
selectedBlogs.forEach((blog) => {
|
||||
dates.push(blog.lastModified);
|
||||
});
|
||||
|
||||
const lastModified = dates.sort((a: Date, b: Date) => {
|
||||
return b.getTime() - a.getTime();
|
||||
});
|
||||
|
||||
sitemaps.push({
|
||||
url: `/sitemap/blogs-${i + 1}.xml`,
|
||||
lastModified: lastModified[0]
|
||||
});
|
||||
}
|
||||
|
||||
let sitemapContent = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${sitemaps.map((item) => `
|
||||
<sitemap>
|
||||
<loc>${settings.website.domainName}${item.url}</loc>
|
||||
<lastmod>${item.lastModified.toISOString()}</lastmod>
|
||||
</sitemap>
|
||||
`).join('')}
|
||||
</sitemapindex>
|
||||
`;
|
||||
|
||||
return new Response(minifyXML(sitemapContent), {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
"Content-Type": "application/xml"
|
||||
}
|
||||
});
|
||||
}) satisfies APIRoute;
|
||||
104
astro/src/pages/sitemap/index.xml.ts
Normal file
104
astro/src/pages/sitemap/index.xml.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { getAllBlogs } from "@/content/blogs/blogs";
|
||||
import { getAllAlbums } from "@/content/photos/albums";
|
||||
import { getAllProjects } from "@/content/projects/projects";
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import type { APIRoute } from "astro";
|
||||
import minifyXML from "minify-xml";
|
||||
|
||||
export const GET = (async () => {
|
||||
const settings = await getSettings();
|
||||
|
||||
let sitemapIndex: SitemapIndex[] = [
|
||||
{
|
||||
url: "/sitemap/pages.xml",
|
||||
lastModified: new Date()
|
||||
}
|
||||
];
|
||||
|
||||
if (settings.blog.enabled) {
|
||||
const blogLastModifieds = [
|
||||
settings.blog.lastModified,
|
||||
settings.sitemap.lastModified,
|
||||
settings.website.lastModified
|
||||
];
|
||||
|
||||
let blogs = await getAllBlogs(settings);
|
||||
|
||||
blogs.forEach((blog) => {
|
||||
blogLastModifieds.push(blog.lastModified);
|
||||
});
|
||||
|
||||
const lastModifiedBlogs = blogLastModifieds.sort((a: Date, b: Date) => {
|
||||
return b.getTime() - a.getTime();
|
||||
});
|
||||
|
||||
sitemapIndex.push({
|
||||
url: "/sitemap/blogs.xml",
|
||||
lastModified: lastModifiedBlogs[0]
|
||||
});
|
||||
};
|
||||
if (settings.project.enabled) {
|
||||
const projectLastModifieds = [
|
||||
settings.project.lastModified,
|
||||
settings.sitemap.lastModified,
|
||||
settings.website.lastModified
|
||||
];
|
||||
|
||||
let projects = await getAllProjects(settings);
|
||||
|
||||
projects.forEach((project) => {
|
||||
projectLastModifieds.push(project.lastModified);
|
||||
});
|
||||
|
||||
const lastModifiedProjects = projectLastModifieds.sort((a: Date, b: Date) => {
|
||||
return b.getTime() - a.getTime();
|
||||
});
|
||||
|
||||
sitemapIndex.push({
|
||||
url: "/sitemap/projects.xml",
|
||||
lastModified: lastModifiedProjects[0]
|
||||
});
|
||||
};
|
||||
if (settings.photo.enabled) {
|
||||
const photoLastModifieds = [
|
||||
settings.photo.lastModified,
|
||||
settings.sitemap.lastModified,
|
||||
settings.website.lastModified
|
||||
];
|
||||
|
||||
let albums = await getAllAlbums(settings);
|
||||
|
||||
albums.forEach((album) => {
|
||||
photoLastModifieds.push(album.lastModified);
|
||||
});
|
||||
|
||||
const lastModifiedAlbums = photoLastModifieds.sort((a: Date, b: Date) => {
|
||||
return b.getTime() - a.getTime();
|
||||
});
|
||||
|
||||
sitemapIndex.push({
|
||||
url: "/sitemap/albums.xml",
|
||||
lastModified: lastModifiedAlbums[0]
|
||||
});
|
||||
};
|
||||
|
||||
let sitemapContent = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${sitemapIndex.map((item) => `
|
||||
<sitemap>
|
||||
<loc>${settings.website.domainName}${item.url}</loc>
|
||||
<lastmod>${item.lastModified.toISOString()}</lastmod>
|
||||
</sitemap>
|
||||
`).join('')}
|
||||
</sitemapindex>
|
||||
`;
|
||||
|
||||
return new Response(minifyXML(sitemapContent), {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
"Content-Type": "application/xml"
|
||||
}
|
||||
});
|
||||
}) satisfies APIRoute;
|
||||
64
astro/src/pages/sitemap/pages-[page].xml.ts
Normal file
64
astro/src/pages/sitemap/pages-[page].xml.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { getAllWebpages } from "@/content/pages/pages";
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import type { APIRoute } from "astro";
|
||||
import minifyXML from "minify-xml";
|
||||
|
||||
export const GET = (async ({ params }) => {
|
||||
const settings = await getSettings();
|
||||
|
||||
const currentPage = params.page;
|
||||
|
||||
const webPages = await getAllWebpages();
|
||||
const selectedPages = webPages.slice(
|
||||
((Number(currentPage) - 1) * settings.sitemap.perPage),
|
||||
Number(currentPage) * settings.sitemap.perPage - 1
|
||||
)
|
||||
|
||||
let pages: SitemapPage[] = [];
|
||||
|
||||
selectedPages.forEach((page) => {
|
||||
if (page.exists) {
|
||||
pages.push({
|
||||
url: page.url,
|
||||
lastModified: page.lastModified
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let sitemapContent = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${pages.map((page) => `
|
||||
<sitemap>
|
||||
<loc>${settings.website.domainName}${page.url}</loc>
|
||||
<lastmod>${page.lastModified.toISOString()}</lastmod>
|
||||
</sitemap>
|
||||
`).join('')}
|
||||
</sitemapindex>
|
||||
`;
|
||||
|
||||
return new Response(minifyXML(sitemapContent), {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
"Content-Type": "application/xml"
|
||||
}
|
||||
});
|
||||
}) satisfies APIRoute;
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const settings = await getSettings();
|
||||
const webPages = await getAllWebpages();
|
||||
|
||||
const pageCount = webPages.length;
|
||||
const perPage = settings.sitemap.perPage;
|
||||
const pages = Math.ceil(pageCount / perPage);
|
||||
|
||||
let items: any[] = [];
|
||||
|
||||
for (let i = 0; i < pages; i++) {
|
||||
items.push({ params: { page: (i + 1).toString() } });
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
42
astro/src/pages/sitemap/pages.xml.ts
Normal file
42
astro/src/pages/sitemap/pages.xml.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { getAllWebpages } from "@/content/pages/pages";
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import type { APIRoute } from "astro";
|
||||
import minifyXML from "minify-xml";
|
||||
|
||||
export const GET = (async () => {
|
||||
const settings = await getSettings();
|
||||
|
||||
const webPages = await getAllWebpages();
|
||||
const pageCount = webPages.length;
|
||||
const perPage = settings.sitemap.perPage;
|
||||
const pages = Math.ceil(pageCount / perPage);
|
||||
|
||||
let sitemaps: SitemapIndex[] = [];
|
||||
|
||||
for (let i = 0; i < pages; i++) {
|
||||
sitemaps.push({
|
||||
url: `/sitemap/pages-${i + 1}.xml`,
|
||||
lastModified: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
let sitemapContent = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${sitemaps.map((item) => `
|
||||
<sitemap>
|
||||
<loc>${settings.website.domainName}${item.url}</loc>
|
||||
<lastmod>${item.lastModified.toISOString()}</lastmod>
|
||||
</sitemap>
|
||||
`).join('')}
|
||||
</sitemapindex>
|
||||
`;
|
||||
|
||||
return new Response(minifyXML(sitemapContent), {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
"Content-Type": "application/xml"
|
||||
}
|
||||
});
|
||||
}) satisfies APIRoute;
|
||||
70
astro/src/pages/sitemap/projects-[page].xml.ts
Normal file
70
astro/src/pages/sitemap/projects-[page].xml.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { getAllProjects } from "@/content/projects/projects";
|
||||
import { getSettings } from "@/content/settings/settings";
|
||||
import { getProjectRoute } from "@/lib/routing";
|
||||
import type { APIRoute } from "astro";
|
||||
import minifyXML from "minify-xml";
|
||||
|
||||
export const GET = (async ({ params }) => {
|
||||
const settings = await getSettings();
|
||||
|
||||
if (!settings.project.enabled) {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
statusText: "Not Found"
|
||||
});
|
||||
}
|
||||
|
||||
const currentPage = params.page;
|
||||
|
||||
const projects = await getAllProjects(settings);
|
||||
const selectedProjects = projects.slice(
|
||||
((Number(currentPage) - 1) * settings.sitemap.perPage),
|
||||
Number(currentPage) * settings.sitemap.perPage - 1
|
||||
);
|
||||
|
||||
let pages: SitemapPage[] = [];
|
||||
|
||||
selectedProjects.forEach((project) => {
|
||||
pages.push({
|
||||
url: getProjectRoute(settings.project, project),
|
||||
lastModified: project.lastModified
|
||||
});
|
||||
});
|
||||
|
||||
let sitemapContent = `
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${pages.map((page) => `
|
||||
<sitemap>
|
||||
<loc>${settings.website.domainName}${page.url}</loc>
|
||||
<lastmod>${page.lastModified.toISOString()}</lastmod>
|
||||
</sitemap>
|
||||
`).join('')}
|
||||
</sitemapindex>
|
||||
`;
|
||||
|
||||
return new Response(minifyXML(sitemapContent), {
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: {
|
||||
"Content-Type": "application/xml"
|
||||
}
|
||||
});
|
||||
}) satisfies APIRoute;
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const settings = await getSettings();
|
||||
const projects = await getAllProjects(settings);
|
||||
|
||||
const projectCount = projects.length;
|
||||
const perPage = settings.sitemap.perPage;
|
||||
const pages = Math.ceil(projectCount / perPage);
|
||||
|
||||
let items: any[] = [];
|
||||
|
||||
for (let i = 0; i < pages; i++) {
|
||||
items.push({ params: { page: (i + 1).toString() } });
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user