61 Commits

Author SHA1 Message Date
itsfinniii
b13935c7c5 Prepare a get files function for later development 2026-04-27 22:24:20 +02:00
itsfinniii
0ef04d5f3e Add minify of website to Astro config 2026-04-27 22:15:19 +02:00
itsfinniii
d9430335d5 Clean up the project a bit
Fix imports, remove unnecessary imports, and replace all single apostrophe to double apostrophes
2026-04-27 21:15:32 +02:00
itsfinniii
9a92b1939c Fix the Photo Layout thumbnail 2026-04-27 20:59:43 +02:00
itsfinniii
b304cc400c Fix thumbnail size for the album 2026-04-27 17:24:08 +02:00
itsfinniii
79e343dbdc Add some comments 2026-04-27 17:11:11 +02:00
itsfinniii
7116aa2348 Fix category index thumbnail 2026-04-27 16:55:43 +02:00
itsfinniii
c112a69f0e Fix category index thumbnail 2026-04-27 16:40:53 +02:00
itsfinniii
4a9d0fb273 Fix thumbnail for Project Posts 2026-04-27 16:29:41 +02:00
itsfinniii
29c47ab72d Fix keywords for BlogLayout 2026-04-27 16:11:37 +02:00
itsfinniii
27b8dc4118 Fix thumbnail for blog posts 2026-04-27 16:08:14 +02:00
itsfinniii
45a2627ec6 Fix thumbnail for web pages 2026-04-27 15:42:38 +02:00
itsfinniii
eba518ccc2 Fix problem with build 2026-04-27 15:34:12 +02:00
itsfinniii
3cfe6697a9 Hopefully fix some problems 2026-04-27 15:28:08 +02:00
itsfinniii
07716dae17 Revert back to Vite 6 2026-04-27 14:52:26 +02:00
itsfinniii
db65ac52a3 Finish the photo page 2026-04-27 14:37:13 +02:00
itsfinniii
fdc8a0aae6 Make first test for photos 2026-04-26 22:26:51 +02:00
itsfinniii
092d2a4458 Add loading spinner to gallery 2026-04-26 16:25:21 +02:00
itsfinniii
760281f7a4 Start with Changelogs.md file 2026-04-26 16:16:43 +02:00
itsfinniii
157cb9389c Increase spacing for justified layout 2026-04-26 16:13:20 +02:00
itsfinniii
be02d749dd Add pagination to album page 2026-04-26 16:12:57 +02:00
itsfinniii
feac162baa Add Photo Album pages 2026-04-26 15:32:59 +02:00
itsfinniii
f86630bcb0 Create base for Albums 2026-04-26 13:52:56 +02:00
itsfinniii
abaea70c7b Add albums to the project 2026-04-25 15:04:47 +02:00
itsfinniii
1fde3d4f69 Update packages 2026-04-24 18:50:58 +02:00
itsfinniii
7b5a8bc705 Fix the colors of the Footer 2026-04-24 18:30:23 +02:00
itsfinniii
d284010e28 Make text part greyer 2026-04-24 18:21:25 +02:00
itsfinniii
66409ab859 Fix Vite version 2026-04-24 18:20:47 +02:00
itsfinniii
46e705f6ea Fix robots.txt 2026-04-19 22:15:22 +02:00
itsfinniii
3bd4de2f30 Migrate to Astro 6, fix sitemaps 2026-04-19 22:05:28 +02:00
itsfinniii
00e4826744 Upgrade to Astro 6 2026-04-19 22:03:24 +02:00
itsfinniii
2744e6173c Update schema.json 2026-04-19 21:59:54 +02:00
itsfinniii
915879beac Prepare menu for website 2026-04-19 21:58:30 +02:00
itsfinniii
6d4a62fae7 Make footer more responsive 2026-04-19 21:28:29 +02:00
itsfinniii
506a5ed14e Add footer to website 2026-04-19 18:03:32 +02:00
itsfinniii
2374a6bd22 Fix return for the LastProjects 2026-04-12 20:13:08 +02:00
itsfinniii
8bc95d0f50 Only render the last components if there is anything to show, otherwise just show nothing 2026-04-12 18:23:07 +02:00
itsfinniii
3485c4583d Change the last blogs component 2026-04-12 18:20:40 +02:00
itsfinniii
ee949aa76f Fix thumbnail size for Last Projects 2026-04-05 22:50:38 +02:00
itsfinniii
07d2c8628f Fix thumbnail for Last Albums 2026-04-05 22:49:17 +02:00
itsfinniii
d39a98a42f Fix image size for categories and albums 2026-04-05 22:43:26 +02:00
itsfinniii
525422105c Fix the image size for Projects 2026-04-05 22:41:43 +02:00
itsfinniii
a0473094cf Fix image size in Blogs 2026-04-05 22:39:35 +02:00
itsfinniii
36004bddb0 Add development domain to astro.config.image 2026-04-05 22:39:28 +02:00
itsfinniii
89bbbf5595 Fix image resize function 2026-04-05 22:39:14 +02:00
itsfinniii
4ccbc9d9a8 Create PhotoLayout for later 2026-04-05 22:27:18 +02:00
itsfinniii
b73066352e Fix thumbnail for Photo 2026-04-05 22:23:31 +02:00
itsfinniii
f95f792775 Fix some issues with the image hashing 2026-04-05 22:10:06 +02:00
itsfinniii
5c161b8381 Make images the correct size by resizing them 2026-04-04 20:28:47 +02:00
itsfinniii
47e50a3ba4 Create function to calculate the new width and height of an image 2026-04-04 18:36:00 +02:00
itsfinniii
cf12428f98 Add tags to the Project page and Blog page 2026-04-04 18:25:18 +02:00
itsfinniii
67362dad96 Reword category meta description 2026-04-04 18:10:17 +02:00
itsfinniii
82587a6211 Add description for Photo Category page 2026-04-03 22:40:47 +02:00
itsfinniii
c05b50877b Add comment to categories index 2026-04-03 22:38:17 +02:00
itsfinniii
9bf417478a Do the same for the project posts 2026-04-03 22:37:54 +02:00
itsfinniii
4f955a9ec6 Change searchEngine for BlogPost in route 2026-04-03 22:37:36 +02:00
itsfinniii
f1b0d269bf Change searchEngine prop for WebpageLayout in route 2026-04-03 22:37:01 +02:00
itsfinniii
32c698c39a Update some metadata for the photo category page 2026-04-03 22:33:41 +02:00
itsfinniii
aa93600c79 Add OG thumbnail to Photo Category Index 2026-04-03 22:16:17 +02:00
itsfinniii
0b45967d30 Require at least one photo category before actually rendering pages of the photos 2026-04-03 22:12:12 +02:00
itsfinniii
39c26e73c6 Fix vulnerable packages 2026-04-03 22:00:15 +02:00
74 changed files with 3092 additions and 2440 deletions

View File

@@ -1,14 +1,45 @@
// @ts-check // @ts-check
import { defineConfig } from 'astro/config'; import { defineConfig } from "astro/config";
import preact from '@astrojs/preact'; import preact from "@astrojs/preact";
import tailwindcss from '@tailwindcss/vite'; import tailwindcss from "@tailwindcss/vite";
import graphql from '@rollup/plugin-graphql'; import graphql from "@rollup/plugin-graphql";
import playformCompress from "@playform/compress";
import { getFiles } from "./src/build/files.ts";
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
integrations: [preact()], integrations: [preact(), playformCompress({
CSS: true,
Image: false,
JSON: true,
JavaScript: false,
HTML: true
}), {
name: "download-files",
hooks: {
"astro:build:start": async ({ }) => {
await getFiles();
}
}
}],
output: "static",
prefetch: true,
build: {
assets: "assets"
},
image: {
domains: ["development.directus.itsfinniii.com"]
},
vite: { vite: {
plugins: [tailwindcss(), graphql()] plugins: [graphql(), tailwindcss()],
resolve: {
alias: {
react: "preact/compat",
"react-dom": "preact/compat",
},
},
optimizeDeps: {
exclude: ["@immich/justified-layout-wasm"]
}
} }
}); });

20
astro/changelogs.md Normal file
View 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

3390
astro/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,12 +9,14 @@
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/preact": "^4.1.3", "@astrojs/preact": "^5.1.2",
"@directus/sdk": "^21.2.0", "@directus/sdk": "^21.2.0",
"@immich/justified-layout-wasm": "^0.4.3",
"@playform/compress": "^0.2.3",
"@rollup/plugin-graphql": "^2.0.5", "@rollup/plugin-graphql": "^2.0.5",
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.2.1", "@tailwindcss/vite": "^4.2.4",
"astro": "^5.17.1", "astro": "^6.1.9",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"markdown-it": "^14.1.1", "markdown-it": "^14.1.1",
"markdown-it-highlightjs": "^4.3.0", "markdown-it-highlightjs": "^4.3.0",
@@ -22,10 +24,14 @@
"mdast-util-to-string": "^4.0.0", "mdast-util-to-string": "^4.0.0",
"minify-xml": "^4.5.2", "minify-xml": "^4.5.2",
"preact": "^10.28.4", "preact": "^10.28.4",
"react-responsive-masonry": "^2.7.2",
"reading-time": "^1.5.0", "reading-time": "^1.5.0",
"tailwindcss": "^4.2.1", "tailwindcss": "^4.2.4",
"tslib": "^2.8.1" "tslib": "^2.8.1"
}, },
"overrides": {
"vite": "^7.0.0"
},
"devDependencies": { "devDependencies": {
"@types/markdown-it": "^14.1.2", "@types/markdown-it": "^14.1.2",
"@types/md5": "^2.3.6" "@types/md5": "^2.3.6"

3
astro/src/build/files.ts Normal file
View File

@@ -0,0 +1,3 @@
// This file gets files, and puts them in the public folder before starting the build.
export async function getFiles() {
}

View File

@@ -1,11 +1,11 @@
--- ---
import { getAllPaginatedBlogs } from '@/content/blogs/blogs'; import { getAllPaginatedBlogs } from "@/content/blogs/blogs";
import { getSettings } from '@/content/settings/settings'; import { getSettings } from "@/content/settings/settings";
import CalendarIcon from '@/icons/CalendarIcon.astro'; import CalendarIcon from "@/icons/CalendarIcon.astro";
import { getImageUrl } from '@/lib/images'; import { getImageSize, getImageUrl } from "@/lib/images";
import { markdownToHtml } from '@/lib/markdown'; import { markdownToHtml } from "@/lib/markdown";
import { getBlogRoute } from '@/lib/routing'; import { getBlogRoute } from "@/lib/routing";
import { Image } from 'astro:assets'; import { Image } from "astro:assets";
interface Props { interface Props {
page: BlogIndex; page: BlogIndex;
@@ -30,14 +30,17 @@ const blogs = await getAllPaginatedBlogs(settings, pageNumber);
</div> </div>
<div class="grid grid-cols-2 gap-6"> <div class="grid grid-cols-2 gap-6">
{ blogs.map((blog) => ( { 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`}> <a href={getBlogRoute(settings.blog, blog)} class={`flex flex-col gap-2`}>
<Image <Image
src={getImageUrl(blog.searchEngine.thumbnail.url)} src={getImageUrl(blog.searchEngine.thumbnail.url)}
alt={blog.title} alt={blog.title}
class="flex rounded-2xl shadow-md w-full" class="flex rounded-2xl shadow-md w-full"
width={600} width={imageSize.width}
height={315} height={imageSize.height}
/> />
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<h4 class="font-semibold text-[28px]">{blog.title}</h4> <h4 class="font-semibold text-[28px]">{blog.title}</h4>
@@ -47,6 +50,8 @@ const blogs = await getAllPaginatedBlogs(settings, pageNumber);
</div> </div>
</div> </div>
</a> </a>
)) } )
}) }
</div> </div>
</div> </div>

View File

@@ -1,9 +1,8 @@
--- ---
import CalendarIcon from '@/icons/CalendarIcon.astro'; import CalendarIcon from "@/icons/CalendarIcon.astro";
import { getImageUrl } from '@/lib/images'; import { markdownToHtml } from "@/lib/markdown";
import { markdownToHtml } from '@/lib/markdown'; import { getTypographyClasses } from "@/styles/markdownClasses";
import { getTypographyClasses } from '@/styles/markdownClasses'; import { Image } from "astro:assets";
import { Image } from 'astro:assets';
interface Props { interface Props {
blog: BlogPost; blog: BlogPost;
@@ -28,9 +27,9 @@ const { blog } = Astro.props;
<div class="aspect-1200/630 w-full max-w-full overflow-hidden"> <div class="aspect-1200/630 w-full max-w-full overflow-hidden">
<div class="w-full h-full rounded-2xl shadow-md object-cover"> <div class="w-full h-full rounded-2xl shadow-md object-cover">
<Image <Image
src={getImageUrl(blog.searchEngine.thumbnail.url)} src={blog.searchEngine.thumbnail.url}
width="1200" width={blog.searchEngine.thumbnail.width}
height="630" height={blog.searchEngine.thumbnail.height}
alt={blog.title} alt={blog.title}
class="rounded-2xl" class="rounded-2xl"
/> />

View 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>

View 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>

View 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 "@/components/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>

View 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>
)
}

View File

@@ -11,9 +11,7 @@ interface Props {
const category = Astro.props.category; const category = Astro.props.category;
const settings = await getSettings(); const settings = await getSettings();
const categoryAlbums = await getCategoryAlbums(settings, category.category); const categoryAlbums = await getCategoryAlbums(settings, category.category.url);
console.log(categoryAlbums);
--- ---
<div <div
@@ -32,8 +30,8 @@ console.log(categoryAlbums);
<Image <Image
src={getImageUrl(album.thumbnail.url)} src={getImageUrl(album.thumbnail.url)}
alt={album.title} alt={album.title}
width="1200" width={album.thumbnail.width}
height="630" height={album.thumbnail.height}
class="rounded-2xl transition-transform duration-300 group-hover:scale-102" class="rounded-2xl transition-transform duration-300 group-hover:scale-102"
/> />

View File

@@ -1,7 +1,6 @@
--- ---
import { getAllCategories } from "@/content/photos/categories"; import { getAllCategories } from "@/content/photos/categories";
import { getSettings } from "@/content/settings/settings" import { getSettings } from "@/content/settings/settings"
import { getImageUrl } from "@/lib/images";
import { getCategoryRoute } from "@/lib/routing"; import { getCategoryRoute } from "@/lib/routing";
import { Image } from "astro:assets"; import { Image } from "astro:assets";
@@ -23,10 +22,10 @@ const categories = await getAllCategories(settings);
<a href={getCategoryRoute(settings.photo, category)} class="group relative block w-[70%] overflow-hidden rounded-2xl shadow-md"> <a href={getCategoryRoute(settings.photo, category)} class="group relative block w-[70%] overflow-hidden rounded-2xl shadow-md">
<div> <div>
<Image <Image
src={getImageUrl(category.thumbnail.url)} src={category.thumbnail.url}
alt={category.title} alt={category.title}
width="1200" width={category.thumbnail.width}
height="630" height={category.thumbnail.height}
class="rounded-2xl transition-transform duration-300 group-hover:scale-102" class="rounded-2xl transition-transform duration-300 group-hover:scale-102"
/> />

View 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>

View File

@@ -1,11 +1,11 @@
--- ---
import { getSettings } from '@/content/settings/settings'; import { getSettings } from "@/content/settings/settings";
import { getAllPaginatedProjects } from '@/content/projects/projects'; import { getAllPaginatedProjects } from "@/content/projects/projects";
import { markdownToHtml } from '@/lib/markdown'; import { markdownToHtml } from "@/lib/markdown";
import { Image } from 'astro:assets'; import { Image } from "astro:assets";
import { getProjectRoute } from '@/lib/routing'; import { getProjectRoute } from "@/lib/routing";
import CalendarIcon from '@/icons/CalendarIcon.astro'; import CalendarIcon from "@/icons/CalendarIcon.astro";
import { getImageUrl } from '@/lib/images'; import { getImageSize, getImageUrl } from "@/lib/images";
interface Props { interface Props {
page: ProjectIndex; page: ProjectIndex;
@@ -30,14 +30,17 @@ const projects = await getAllPaginatedProjects(settings, pageNumber);
</div> </div>
<div class="grid grid-cols-2 gap-6"> <div class="grid grid-cols-2 gap-6">
{ projects.map((project) => ( { 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`}> <a href={getProjectRoute(settings.project, project)} class={`flex flex-col gap-2`}>
<Image <Image
src={getImageUrl(project.searchEngine.thumbnail.url)} src={getImageUrl(project.searchEngine.thumbnail.url)}
alt={project.title} alt={project.title}
class="flex rounded-2xl shadow-md w-full" class="flex rounded-2xl shadow-md w-full"
width={600} width={imageSize.width}
height={315} height={imageSize.height}
/> />
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<h4 class="font-semibold text-[28px]">{project.title}</h4> <h4 class="font-semibold text-[28px]">{project.title}</h4>
@@ -47,6 +50,7 @@ const projects = await getAllPaginatedProjects(settings, pageNumber);
</div> </div>
</div> </div>
</a> </a>
)) } )
}) }
</div> </div>
</div> </div>

View File

@@ -1,15 +1,17 @@
--- ---
import CalendarIcon from '@/icons/CalendarIcon.astro'; import CalendarIcon from "@/icons/CalendarIcon.astro";
import { getImageUrl } from '@/lib/images'; import { getImageSize } from "@/lib/images";
import { markdownToHtml } from '@/lib/markdown'; import { markdownToHtml } from "@/lib/markdown";
import { getTypographyClasses } from '@/styles/markdownClasses'; import { getTypographyClasses } from "@/styles/markdownClasses";
import { Image } from 'astro:assets'; import { Image } from "astro:assets";
interface Props { interface Props {
project: ProjectPost; project: ProjectPost;
} }
const { project } = Astro.props; const { project } = Astro.props;
const imageSize = getImageSize(project.searchEngine.thumbnail.width, project.searchEngine.thumbnail.height, 1);
--- ---
<div <div
@@ -28,9 +30,9 @@ const { project } = Astro.props;
<div class="aspect-1200/630 w-full max-w-full overflow-hidden"> <div class="aspect-1200/630 w-full max-w-full overflow-hidden">
<div class="w-full h-full rounded-2xl shadow-md object-cover"> <div class="w-full h-full rounded-2xl shadow-md object-cover">
<Image <Image
src={getImageUrl(project.searchEngine.thumbnail.url)} src={project.searchEngine.thumbnail.url}
width="1200" width={imageSize.width}
height="630" height={imageSize.height}
alt={project.title} alt={project.title}
class="rounded-2xl" class="rounded-2xl"
/> />

View File

@@ -1,6 +1,6 @@
--- ---
import { markdownToHtml } from '@/lib/markdown'; import { markdownToHtml } from "@/lib/markdown";
import { Image } from 'astro:assets'; import { Image } from "astro:assets";
interface Props { interface Props {
contact: ContactComponent; contact: ContactComponent;

View File

@@ -1,6 +1,6 @@
--- ---
import { markdownToHtml } from '@/lib/markdown'; import { markdownToHtml } from "@/lib/markdown";
import { Image } from 'astro:assets'; import { Image } from "astro:assets";
interface Props { interface Props {
equipment: EquipmentTableComponent; equipment: EquipmentTableComponent;

View File

@@ -1,6 +1,6 @@
--- ---
import { markdownToHtml } from '@/lib/markdown'; import { markdownToHtml } from "@/lib/markdown";
import { QuestionList } from '@/components/web/subcomponents/QuestionList.tsx'; import { QuestionList } from "@/components/web/subcomponents/QuestionList.tsx";
interface Props { interface Props {
faq: FrequentlyAskedQuestionsComponent; faq: FrequentlyAskedQuestionsComponent;

View File

@@ -1,5 +1,5 @@
--- ---
import { Image } from 'astro:assets'; import { Image } from "astro:assets";
interface Props { interface Props {
hero: HeroComponent; hero: HeroComponent;

View File

@@ -2,7 +2,7 @@
import { getLastAlbums } from "@/content/photos/albums"; import { getLastAlbums } from "@/content/photos/albums";
import { getSettings } from "@/content/settings/settings"; import { getSettings } from "@/content/settings/settings";
import CalendarIcon from "@/icons/CalendarIcon.astro"; import CalendarIcon from "@/icons/CalendarIcon.astro";
import { getImageUrl } from "@/lib/images"; import { getImageSize, getImageUrl } from "@/lib/images";
import { getAlbumRoute } from "@/lib/routing"; import { getAlbumRoute } from "@/lib/routing";
import { Image } from "astro:assets"; import { Image } from "astro:assets";
@@ -25,7 +25,7 @@ const lastAlbums = await getLastAlbums(albums.amount);
const size = calculateSizeClasses(albums.amount, lastAlbums.length); const size = calculateSizeClasses(albums.amount, lastAlbums.length);
--- ---
{ settings.photo.enabled && ( { (settings.photo.enabled && lastAlbums.length > 0) && (
<div <div
id={`lastalbums-${albums.id}`} 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" class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18 w-full"
@@ -44,14 +44,17 @@ const size = calculateSizeClasses(albums.amount, lastAlbums.length);
{ lastAlbums.length >= 4 ? ( { 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"> <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) => ( { 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`}> <a href={getAlbumRoute(settings.photo, album)} class={`w-full flex flex-col gap-2`}>
<Image <Image
src={getImageUrl(album.thumbnail.url)} src={getImageUrl(album.thumbnail.url)}
alt={album.title} alt={album.title}
class="flex rounded-2xl shadow-md w-full" class="flex rounded-2xl shadow-md w-full"
width={600} width={imageSize.width}
height={315} height={imageSize.height}
/> />
<h4 class="font-semibold text-[28px]">{album.title}</h4> <h4 class="font-semibold text-[28px]">{album.title}</h4>
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm"> <div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
@@ -59,19 +62,23 @@ const size = calculateSizeClasses(albums.amount, lastAlbums.length);
<div>{album.startDate}</div> <div>{album.startDate}</div>
</div> </div>
</a> </a>
)) } )
}) }
</div> </div>
) : ( ) : (
<div class="flex flex-col lg:flex-row lg:justify-between gap-y-6"> <div class="flex flex-col lg:flex-row lg:justify-between gap-y-6">
{ lastAlbums.map((album) => ( { 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`}> <a href={getAlbumRoute(settings.photo, album)} class={`${size} flex flex-col gap-2`}>
<Image <Image
src={getImageUrl(album.thumbnail.url)} src={getImageUrl(album.thumbnail.url)}
alt={album.title} alt={album.title}
class="flex rounded-2xl shadow-md w-full" class="flex rounded-2xl shadow-md w-full"
width={600} width={imageSize.width}
height={315} height={imageSize.height}
/> />
<h4 class="font-semibold text-[28px]">{album.title}</h4> <h4 class="font-semibold text-[28px]">{album.title}</h4>
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm"> <div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
@@ -79,7 +86,9 @@ const size = calculateSizeClasses(albums.amount, lastAlbums.length);
<div>{album.startDate}</div> <div>{album.startDate}</div>
</div> </div>
</a> </a>
)) } )
}) }
</div> </div>
) } ) }
</div> </div>

View File

@@ -1,10 +1,10 @@
--- ---
import { getLastBlogs } from '@/content/blogs/blogs'; import { getLastBlogs } from "@/content/blogs/blogs";
import { getSettings } from '@/content/settings/settings'; import { getSettings } from "@/content/settings/settings";
import CalendarIcon from '@/icons/CalendarIcon.astro'; import CalendarIcon from "@/icons/CalendarIcon.astro";
import { getImageUrl } from '@/lib/images'; import { getImageSize, getImageUrl } from "@/lib/images";
import { getBlogRoute } from '@/lib/routing'; import { getBlogRoute } from "@/lib/routing";
import { Image } from 'astro:assets'; import { Image } from "astro:assets";
interface Props { interface Props {
blogs: LastBlogsComponent; blogs: LastBlogsComponent;
@@ -25,7 +25,7 @@ const lastBlogs = await getLastBlogs(blogs.amount);
const size = calculateSizeClasses(blogs.amount, lastBlogs.length); const size = calculateSizeClasses(blogs.amount, lastBlogs.length);
--- ---
{ settings.blog.enabled && ( { (settings.blog.enabled && lastBlogs.length > 0) && (
<div <div
id={`lastblogs-${blogs.id}`} 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" class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18 w-full"
@@ -43,14 +43,17 @@ const size = calculateSizeClasses(blogs.amount, lastBlogs.length);
</div> </div>
<div class="flex flex-col lg:flex-row lg:justify-between gap-y-6"> <div class="flex flex-col lg:flex-row lg:justify-between gap-y-6">
{ lastBlogs.map((blog) => ( { 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`}> <a href={getBlogRoute(settings.blog, blog)} class={`${size} flex flex-col gap-2`}>
<Image <Image
src={getImageUrl(blog.searchEngine.thumbnail.url)} src={getImageUrl(blog.searchEngine.thumbnail.url)}
alt={blog.title} alt={blog.title}
class="flex rounded-2xl shadow-md w-full" class="flex rounded-2xl shadow-md w-full"
width={600} width={imageSize.width}
height={315} height={imageSize.height}
/> />
<h4 class="font-semibold text-[28px]">{blog.title}</h4> <h4 class="font-semibold text-[28px]">{blog.title}</h4>
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm"> <div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
@@ -58,7 +61,8 @@ const size = calculateSizeClasses(blogs.amount, lastBlogs.length);
<div>{blog.date}</div> <div>{blog.date}</div>
</div> </div>
</a> </a>
)) } )
}) }
</div> </div>
</div> </div>
) } ) }

View File

@@ -1,10 +1,10 @@
--- ---
import { getLastProjects } from '@/content/projects/projects'; import { getLastProjects } from "@/content/projects/projects";
import { getSettings } from '@/content/settings/settings'; import { getSettings } from "@/content/settings/settings";
import CalendarIcon from '@/icons/CalendarIcon.astro'; import CalendarIcon from "@/icons/CalendarIcon.astro";
import { getImageUrl } from '@/lib/images'; import { getImageSize, getImageUrl } from "@/lib/images";
import { getProjectRoute } from '@/lib/routing'; import { getProjectRoute } from "@/lib/routing";
import { Image } from 'astro:assets'; import { Image } from "astro:assets";
interface Props { interface Props {
projects: LastProjectsComponent; projects: LastProjectsComponent;
@@ -25,7 +25,7 @@ const lastProjects = await getLastProjects(projects.amount);
const size = calculateSizeClasses(projects.amount, lastProjects.length); const size = calculateSizeClasses(projects.amount, lastProjects.length);
--- ---
{ settings.project.enabled && ( { (settings.project.enabled && lastProjects.length > 0) && (
<div <div
id={`lastprojects-${projects.id}`} 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" class="flex lg:flex-col flex-col py-12 px-12 lg:container mx-auto gap-y-8 gap-x-18 w-full"
@@ -43,14 +43,17 @@ const size = calculateSizeClasses(projects.amount, lastProjects.length);
</div> </div>
<div class="flex flex-col lg:flex-row lg:justify-between gap-y-6"> <div class="flex flex-col lg:flex-row lg:justify-between gap-y-6">
{ lastProjects.map((project) => ( { 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`}> <a href={getProjectRoute(settings.project, project)} class={`${size} flex flex-col gap-2`}>
<Image <Image
src={getImageUrl(project.searchEngine.thumbnail.url)} src={getImageUrl(project.searchEngine.thumbnail.url)}
alt={project.title} alt={project.title}
class="flex rounded-2xl shadow-md w-full" class="flex rounded-2xl shadow-md w-full"
width={600} width={imageSize.width}
height={315} height={imageSize.height}
/> />
<h4 class="font-semibold text-[28px]">{project.title}</h4> <h4 class="font-semibold text-[28px]">{project.title}</h4>
<div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm"> <div class="flex flex-row items-center gap-1.5 text-neutral-900 text-sm">
@@ -58,7 +61,8 @@ const size = calculateSizeClasses(projects.amount, lastProjects.length);
<div>{project.date}</div> <div>{project.date}</div>
</div> </div>
</a> </a>
)) } )
}) }
</div> </div>
</div> </div>
) } ) }

View File

@@ -1,6 +1,6 @@
--- ---
import { markdownToHtml } from '@/lib/markdown'; import { markdownToHtml } from "@/lib/markdown";
import StarRating from './subcomponents/StarRating.astro'; import StarRating from "./subcomponents/StarRating.astro";
interface Props { interface Props {
reviews: ReviewListComponent; reviews: ReviewListComponent;
@@ -37,7 +37,7 @@ const reviewsToShow = reviews.reviews.slice(0, 5);
<div class="flex flex-col gap-6.5"> <div class="flex flex-col gap-6.5">
{ reviewsToShow.map((review) => ( { reviewsToShow.map((review) => (
<div class="flex flex-col justify-center gap-3 bg-neutral-50 py-4 px-5.5 rounded-2xl shadow-sm"> <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"> <div class="flex flex-col justify-center gap-1.25">
<h4 class="text-2xl font-semibold">{review.name}</h4> <h4 class="text-2xl font-semibold">{review.name}</h4>
<div> <div>

View File

@@ -1,6 +1,6 @@
--- ---
import { markdownToHtml } from '@/lib/markdown'; import { markdownToHtml } from "@/lib/markdown";
import { Image } from 'astro:assets'; import { Image } from "astro:assets";
interface Props { interface Props {
textWithImage: TextWithImageComponent; textWithImage: TextWithImageComponent;

View File

@@ -1,8 +1,8 @@
--- ---
import CalendarIcon from '@/icons/CalendarIcon.astro'; import CalendarIcon from "@/icons/CalendarIcon.astro";
import { Image } from 'astro:assets'; import { Image } from "astro:assets";
import { upcomingEvent as UpcomingEvent } from './subcomponents/UpcomingEvent'; import { upcomingEvent as UpcomingEvent } from "./subcomponents/UpcomingEvent";
import { markdownToHtml } from '@/lib/markdown'; import { markdownToHtml } from "@/lib/markdown";
interface Props { interface Props {
upcomingEvents: UpcomingEventsComponent; upcomingEvents: UpcomingEventsComponent;

View File

@@ -1,15 +1,15 @@
--- ---
import FrequentlyAskedQuestions from '../web/FrequentlyAskedQuestions.astro'; import FrequentlyAskedQuestions from '@/components/web/FrequentlyAskedQuestions.astro';
import Hero from '../web/Hero.astro'; import Hero from '@/components/web/Hero.astro';
import TextWithImage from '../web/TextWithImage.astro'; import TextWithImage from '@/components/web/TextWithImage.astro';
import UpcomingEvents from '../web/UpcomingEvents.astro'; import UpcomingEvents from '@/components/web/UpcomingEvents.astro';
import WallOfText from '../web/WallOfText.astro'; import WallOfText from '@/components/web/WallOfText.astro';
import EquipmentTable from '../web/EquipmentTable.astro'; import EquipmentTable from '@/components/web/EquipmentTable.astro';
import Reviews from '../web/Reviews.astro'; import Reviews from '@/components/web/Reviews.astro';
import LastBlogs from '../web/LastBlogs.astro'; import LastBlogs from '@/components/web/LastBlogs.astro';
import LastProjects from '../web/LastProjects.astro'; import LastProjects from '@/components/web/LastProjects.astro';
import LastAlbums from '../web/LastAlbums.astro'; import LastAlbums from '@/components/web/LastAlbums.astro';
import Contact from '../web/Contact.astro'; import Contact from '@/components/web/Contact.astro';
interface Props { interface Props {
webpage: WebpageComponent[]; webpage: WebpageComponent[];

View File

@@ -1,10 +1,12 @@
import { createDirectusConnection } from "@/lib/directus"; import { createDirectusConnection } from "@/lib/directus";
import { print } from 'graphql'; import { print } from "graphql";
import getBlogs from '@/graphql/blogs/getBlogs.graphql'; import getBlogs from "@/graphql/blogs/getBlogs.graphql";
import getBlogPost from '@/graphql/blogs/getBlog.graphql'; import getBlogPost from "@/graphql/blogs/getBlog.graphql";
import getLastBlogPosts from '@/graphql/blogs/getLastBlogPosts.graphql'; import getLastBlogPosts from "@/graphql/blogs/getLastBlogPosts.graphql";
import getPaginatedBlogs from '@/graphql/blogs/getPaginatedBlogs.graphql'; import getPaginatedBlogs from "@/graphql/blogs/getPaginatedBlogs.graphql";
import { formatDate } from "@/lib/dates"; import { formatDate } from "@/lib/dates";
import { getImageSize, getImageUrl } from "@/lib/images";
import { getImage } from "astro:assets";
export async function getAllBlogs(settings: GlobalSettings): Promise<BlogPost[]> { export async function getAllBlogs(settings: GlobalSettings): Promise<BlogPost[]> {
const client = await createDirectusConnection(); const client = await createDirectusConnection();
@@ -25,6 +27,9 @@ export async function getAllBlogs(settings: GlobalSettings): Promise<BlogPost[]>
blogRecord["search_engine"][0]["thumbnail"]["created_on"] 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 = { const blog: BlogPost = {
exists: true, exists: true,
type: "BlogPost", type: "BlogPost",
@@ -34,6 +39,11 @@ export async function getAllBlogs(settings: GlobalSettings): Promise<BlogPost[]>
content: blogRecord["content"], content: blogRecord["content"],
date: blogRecord["date"], date: blogRecord["date"],
url: blogRecord["url"], 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: { searchEngine: {
title: blogRecord["search_engine"][0]["title"], title: blogRecord["search_engine"][0]["title"],
description: blogRecord["search_engine"][0]["description"], description: blogRecord["search_engine"][0]["description"],
@@ -42,8 +52,8 @@ export async function getAllBlogs(settings: GlobalSettings): Promise<BlogPost[]>
priority: blogRecord["search_engine"][0]["priority"], priority: blogRecord["search_engine"][0]["priority"],
thumbnail: { thumbnail: {
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"], url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"],
height: blogRecord["search_engine"][0]["thumbnail"]["height"], width: blogThumbnailImage.width,
width: blogRecord["search_engine"][0]["thumbnail"]["width"] height: blogThumbnailImage.height
} }
}, },
tags: [] tags: []
@@ -95,6 +105,16 @@ export async function getBlog(settings: GlobalSettings, route: string): Promise<
blogRecord["search_engine"][0]["thumbnail"]["created_on"] 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 = { const blog: BlogPost = {
exists: true, exists: true,
type: "BlogPost", type: "BlogPost",
@@ -104,6 +124,11 @@ export async function getBlog(settings: GlobalSettings, route: string): Promise<
content: blogRecord["content"], content: blogRecord["content"],
date: blogRecord["date"], date: blogRecord["date"],
url: blogRecord["url"], 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: { searchEngine: {
title: blogRecord["search_engine"][0]["title"], title: blogRecord["search_engine"][0]["title"],
description: blogRecord["search_engine"][0]["description"], description: blogRecord["search_engine"][0]["description"],
@@ -111,9 +136,9 @@ export async function getBlog(settings: GlobalSettings, route: string): Promise<
canonical: blogRecord["search_engine"][0]["canonical"], canonical: blogRecord["search_engine"][0]["canonical"],
priority: blogRecord["search_engine"][0]["priority"], priority: blogRecord["search_engine"][0]["priority"],
thumbnail: { thumbnail: {
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"], url: `${settings.website.domainName}${thumbnail.src}`,
height: blogRecord["search_engine"][0]["thumbnail"]["height"], width: blogThumbnailImage.width,
width: blogRecord["search_engine"][0]["thumbnail"]["width"] height: blogThumbnailImage.height
} }
}, },
tags: [] tags: []
@@ -162,6 +187,9 @@ export async function getLastBlogs(amount: number): Promise<BlogPost[]> {
blogRecord["search_engine"][0]["thumbnail"]["created_on"] 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 = { const blog: BlogPost = {
exists: true, exists: true,
type: "BlogPost", type: "BlogPost",
@@ -171,6 +199,11 @@ export async function getLastBlogs(amount: number): Promise<BlogPost[]> {
content: blogRecord["content"], content: blogRecord["content"],
date: blogRecord["date"], date: blogRecord["date"],
url: blogRecord["url"], 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: { searchEngine: {
title: blogRecord["search_engine"][0]["title"], title: blogRecord["search_engine"][0]["title"],
description: blogRecord["search_engine"][0]["description"], description: blogRecord["search_engine"][0]["description"],
@@ -179,8 +212,8 @@ export async function getLastBlogs(amount: number): Promise<BlogPost[]> {
priority: blogRecord["search_engine"][0]["priority"], priority: blogRecord["search_engine"][0]["priority"],
thumbnail: { thumbnail: {
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"], url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"],
height: blogRecord["search_engine"][0]["thumbnail"]["height"], width: blogThumbnailImage.width,
width: blogRecord["search_engine"][0]["thumbnail"]["width"] height: blogThumbnailImage.height
} }
}, },
tags: [] tags: []
@@ -235,6 +268,9 @@ export async function getAllPaginatedBlogs(settings: GlobalSettings, page: numbe
blogRecord["search_engine"][0]["thumbnail"]["created_on"] 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 = { const blog: BlogPost = {
exists: true, exists: true,
type: "BlogPost", type: "BlogPost",
@@ -244,6 +280,11 @@ export async function getAllPaginatedBlogs(settings: GlobalSettings, page: numbe
content: blogRecord["content"], content: blogRecord["content"],
date: blogRecord["date"], date: blogRecord["date"],
url: blogRecord["url"], 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: { searchEngine: {
title: blogRecord["search_engine"][0]["title"], title: blogRecord["search_engine"][0]["title"],
description: blogRecord["search_engine"][0]["description"], description: blogRecord["search_engine"][0]["description"],
@@ -252,8 +293,8 @@ export async function getAllPaginatedBlogs(settings: GlobalSettings, page: numbe
priority: blogRecord["search_engine"][0]["priority"], priority: blogRecord["search_engine"][0]["priority"],
thumbnail: { thumbnail: {
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"], url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"],
height: blogRecord["search_engine"][0]["thumbnail"]["height"], width: blogThumbnailImage.width,
width: blogRecord["search_engine"][0]["thumbnail"]["width"] height: blogThumbnailImage.height
} }
}, },
tags: [] tags: []

View 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;
}

View 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;
}

View File

@@ -1,9 +1,9 @@
import { createDirectusConnection } from "@/lib/directus"; import { createDirectusConnection } from "@/lib/directus";
import { print } from 'graphql'; import { print } from "graphql";
import { formatDate } from "@/lib/dates"; import { formatDate } from "@/lib/dates";
import getAllPages from "@/graphql/pages/getAllPages.graphql"; import getAllPages from "@/graphql/pages/getAllPages.graphql";
import getPage from "@/graphql/pages/getPage.graphql"; import getPage from "@/graphql/pages/getPage.graphql";
import { getImageUrl } from "@/lib/images"; import { getImageSize, getImageUrl } from "@/lib/images";
export function dataToPage(pageRecord: any): WebPage { export function dataToPage(pageRecord: any): WebPage {
let dates: string[] = [ let dates: string[] = [
@@ -22,6 +22,9 @@ export function dataToPage(pageRecord: any): WebPage {
switch (componentRecord["item"]["__typename"]) { switch (componentRecord["item"]["__typename"]) {
case "Hero": case "Hero":
const resizedHeroImage =
getImageSize(component["background_image"]["width"], component["background_image"]["height"], 2.5);
let heroComponent: HeroComponent = { let heroComponent: HeroComponent = {
component: "Hero", component: "Hero",
id: component["hero_id"], id: component["hero_id"],
@@ -29,8 +32,8 @@ export function dataToPage(pageRecord: any): WebPage {
text: component["hero_text"], text: component["hero_text"],
backgroundImage: { backgroundImage: {
url: getImageUrl(component["background_image"]["filename_disk"]), url: getImageUrl(component["background_image"]["filename_disk"]),
width: component["background_image"]["width"], width: resizedHeroImage.width,
height: component["background_image"]["height"] height: resizedHeroImage.height
} }
}; };
@@ -41,6 +44,9 @@ export function dataToPage(pageRecord: any): WebPage {
break; break;
case "Text_With_Side_Image": case "Text_With_Side_Image":
const resizedTextWithSideImage =
getImageSize(component["image"]["width"], component["image"]["height"], 1.5);
let textWithImageComponent: TextWithImageComponent = { let textWithImageComponent: TextWithImageComponent = {
component: "TextWithImage", component: "TextWithImage",
id: component["twsi_id"], id: component["twsi_id"],
@@ -50,8 +56,8 @@ export function dataToPage(pageRecord: any): WebPage {
imageSize: component["twsi_image_size"], imageSize: component["twsi_image_size"],
image: { image: {
url: getImageUrl(component["image"]["filename_disk"]), url: getImageUrl(component["image"]["filename_disk"]),
width: component["image"]["width"], width: resizedTextWithSideImage.width,
height: component["image"]["height"] height: resizedTextWithSideImage.height
} }
}; };
@@ -109,6 +115,9 @@ export function dataToPage(pageRecord: any): WebPage {
}; };
component["events"].forEach((eventRecord: any) => { component["events"].forEach((eventRecord: any) => {
const resizedThumbnailImage =
getImageSize(eventRecord["thumbnail"]["width"], eventRecord["thumbnail"]["height"], 1);
upcomingEventsComponent.events.push({ upcomingEventsComponent.events.push({
id: eventRecord["id"], id: eventRecord["id"],
title: eventRecord["title"], title: eventRecord["title"],
@@ -120,8 +129,8 @@ export function dataToPage(pageRecord: any): WebPage {
], ],
thumbnail: { thumbnail: {
url: getImageUrl(eventRecord["thumbnail"]["filename_disk"]), url: getImageUrl(eventRecord["thumbnail"]["filename_disk"]),
width: eventRecord["thumbnail"]["width"], width: resizedThumbnailImage.width,
height: eventRecord["thumbnail"]["height"] height: resizedThumbnailImage.height
}, },
startDate: eventRecord["start_date"], startDate: eventRecord["start_date"],
endDate: eventRecord["end_date"] endDate: eventRecord["end_date"]
@@ -177,6 +186,9 @@ export function dataToPage(pageRecord: any): WebPage {
}; };
component["reviews"].forEach((reviewRecord: any) => { component["reviews"].forEach((reviewRecord: any) => {
const reviewThumbnailImage =
getImageSize(reviewRecord["thumbnail"]["width"], reviewRecord["thumbnail"]["height"], 1);
reviewsComponent.reviews.push({ reviewsComponent.reviews.push({
id: reviewRecord["id"], id: reviewRecord["id"],
name: reviewRecord["name"], name: reviewRecord["name"],
@@ -185,8 +197,8 @@ export function dataToPage(pageRecord: any): WebPage {
date: reviewRecord["date"], date: reviewRecord["date"],
thumbnail: { thumbnail: {
url: getImageUrl(reviewRecord["thumbnail"]["filename_disk"]), url: getImageUrl(reviewRecord["thumbnail"]["filename_disk"]),
width: reviewRecord["thumbnail"]["width"], width: reviewThumbnailImage.width,
height: reviewRecord["thumbnail"]["height"] height: reviewThumbnailImage.height
} }
}); });
@@ -292,6 +304,9 @@ export function dataToPage(pageRecord: any): WebPage {
lastModified = new Date(sortedDates[0]); lastModified = new Date(sortedDates[0]);
} }
const thumbnailImage =
getImageSize(searchEngine["thumbnail"]["width"], searchEngine["thumbnail"]["width"], 0.756);
let page: WebPage = { let page: WebPage = {
type: "Webpage", type: "Webpage",
exists: true, exists: true,
@@ -306,8 +321,8 @@ export function dataToPage(pageRecord: any): WebPage {
priority: searchEngine["priority"], priority: searchEngine["priority"],
thumbnail: { thumbnail: {
url: getImageUrl(searchEngine["thumbnail"]["filename_disk"]), url: getImageUrl(searchEngine["thumbnail"]["filename_disk"]),
height: searchEngine["thumbnail"]["height"], height: thumbnailImage.height,
width: searchEngine["thumbnail"]["width"] width: thumbnailImage.width
} }
}, },
components: components components: components

View File

@@ -1,10 +1,11 @@
import { createDirectusConnection } from "@/lib/directus"; import { createDirectusConnection } from "@/lib/directus";
import { print } from "graphql"; import { print } from "graphql";
import getAlbums from '@/graphql/photos/getAlbums.graphql'; import getAlbums from "@/graphql/photos/getAlbums.graphql";
import getAlbumItem from '@/graphql/photos/getAlbum.graphql'; import getAlbumItem from "@/graphql/photos/getAlbum.graphql";
import getLastAlbumsQuery from '@/graphql/photos/getLastAlbums.graphql'; import getLastAlbumsQuery from "@/graphql/photos/getLastAlbums.graphql";
import getCategoryAlbumQuery from '@/graphql/photos/getCategoryAlbum.graphql'; import getCategoryAlbumQuery from "@/graphql/photos/getCategoryAlbum.graphql";
import { formatDate } from "@/lib/dates"; import { formatDate } from "@/lib/dates";
import { getImageSize } from "@/lib/images";
export async function getAllAlbums(settings: GlobalSettings): Promise<PhotoAlbum[]> { export async function getAllAlbums(settings: GlobalSettings): Promise<PhotoAlbum[]> {
const client = await createDirectusConnection(); const client = await createDirectusConnection();
@@ -23,9 +24,16 @@ export async function getAllAlbums(settings: GlobalSettings): Promise<PhotoAlbum
albumRecord["thumbnail"]["created_on"], 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 = { const album: PhotoAlbum = {
exists: true, exists: true,
type: "PhotoAlbum", type: "PhotoAlbum",
id: albumRecord["id"],
title: albumRecord["title"], title: albumRecord["title"],
description: albumRecord["description"], description: albumRecord["description"],
url: albumRecord["url"], url: albumRecord["url"],
@@ -38,14 +46,14 @@ export async function getAllAlbums(settings: GlobalSettings): Promise<PhotoAlbum
url: albumRecord["category"][0]["Photo_Categories_id"]["url"], url: albumRecord["category"][0]["Photo_Categories_id"]["url"],
thumbnail: { thumbnail: {
url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"], url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"],
height: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["height"], width: categoryThumbnailImage.width,
width: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["width"] height: categoryThumbnailImage.height
} }
}, },
thumbnail: { thumbnail: {
url: albumRecord["thumbnail"]["filename_disk"], url: albumRecord["thumbnail"]["filename_disk"],
height: albumRecord["thumbnail"]["height"], width: thumbnailImage.width,
width: albumRecord["thumbnail"]["width"] height: thumbnailImage.height
}, },
photos: [], photos: [],
lastModified: new Date() lastModified: new Date()
@@ -100,9 +108,16 @@ export async function getAlbum(settings: GlobalSettings, route: string): Promise
albumRecord["thumbnail"]["created_on"], 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 = { const album: PhotoAlbum = {
exists: true, exists: true,
type: "PhotoAlbum", type: "PhotoAlbum",
id: albumRecord["id"],
title: albumRecord["title"], title: albumRecord["title"],
description: albumRecord["description"], description: albumRecord["description"],
url: albumRecord["url"], url: albumRecord["url"],
@@ -115,14 +130,14 @@ export async function getAlbum(settings: GlobalSettings, route: string): Promise
url: albumRecord["category"][0]["Photo_Categories_id"]["url"], url: albumRecord["category"][0]["Photo_Categories_id"]["url"],
thumbnail: { thumbnail: {
url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"], url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"],
height: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["height"], width: categoryThumbnailImage.width,
width: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["width"] height: categoryThumbnailImage.height
} }
}, },
thumbnail: { thumbnail: {
url: albumRecord["thumbnail"]["filename_download"], url: albumRecord["thumbnail"]["filename_disk"],
height: albumRecord["thumbnail"]["height"], width: thumbnailImage.width,
width: albumRecord["thumbnail"]["width"] height: thumbnailImage.height
}, },
photos: [], photos: [],
lastModified: new Date() lastModified: new Date()
@@ -174,9 +189,16 @@ export async function getLastAlbums(amount: number) {
albumRecord["thumbnail"]["created_on"], 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 = { const album: PhotoAlbum = {
exists: true, exists: true,
type: "PhotoAlbum", type: "PhotoAlbum",
id: albumRecord["id"],
title: albumRecord["title"], title: albumRecord["title"],
description: albumRecord["description"], description: albumRecord["description"],
url: albumRecord["url"], url: albumRecord["url"],
@@ -189,14 +211,14 @@ export async function getLastAlbums(amount: number) {
url: albumRecord["category"][0]["Photo_Categories_id"]["url"], url: albumRecord["category"][0]["Photo_Categories_id"]["url"],
thumbnail: { thumbnail: {
url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"], url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"],
height: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["height"], width: categoryThumbnailImage.width,
width: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["width"] height: categoryThumbnailImage.height
} }
}, },
thumbnail: { thumbnail: {
url: albumRecord["thumbnail"]["filename_disk"], url: albumRecord["thumbnail"]["filename_disk"],
height: albumRecord["thumbnail"]["height"], width: thumbnailImage.width,
width: albumRecord["thumbnail"]["width"] height: thumbnailImage.height
}, },
photos: [], photos: [],
lastModified: new Date() lastModified: new Date()
@@ -239,7 +261,7 @@ export async function getCategoryAlbums(settings: GlobalSettings, route: string)
const client = await createDirectusConnection(); const client = await createDirectusConnection();
const result = await client.query(print(getCategoryAlbumQuery), { const result = await client.query(print(getCategoryAlbumQuery), {
date: formatDate(new Date(), "%Y-%M-%D"), date: formatDate(new Date(), "%Y-%M-%D"),
categoryUrl: `/${route}` categoryUrl: route
}); });
let albums: PhotoAlbum[] = []; let albums: PhotoAlbum[] = [];
@@ -253,9 +275,16 @@ export async function getCategoryAlbums(settings: GlobalSettings, route: string)
albumRecord["thumbnail"]["created_on"], 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 = { const album: PhotoAlbum = {
exists: true, exists: true,
type: "PhotoAlbum", type: "PhotoAlbum",
id: albumRecord["id"],
title: albumRecord["title"], title: albumRecord["title"],
description: albumRecord["description"], description: albumRecord["description"],
url: albumRecord["url"], url: albumRecord["url"],
@@ -268,20 +297,23 @@ export async function getCategoryAlbums(settings: GlobalSettings, route: string)
url: albumRecord["category"][0]["Photo_Categories_id"]["url"], url: albumRecord["category"][0]["Photo_Categories_id"]["url"],
thumbnail: { thumbnail: {
url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"], url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"],
height: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["height"], width: categoryThumbnailImage.width,
width: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["width"] height: categoryThumbnailImage.height
} }
}, },
thumbnail: { thumbnail: {
url: albumRecord["thumbnail"]["filename_disk"], url: albumRecord["thumbnail"]["filename_disk"],
height: albumRecord["thumbnail"]["height"], width: thumbnailImage.width,
width: albumRecord["thumbnail"]["width"] height: thumbnailImage.height
}, },
photos: [], photos: [],
lastModified: new Date() lastModified: new Date()
}; };
albumRecord["photos"].forEach((photoRecord: any) => { albumRecord["photos"].forEach((photoRecord: any) => {
const imageSize =
getImageSize(photoRecord["photo"]["width"], photoRecord["photo"]["height"], 0.8);
album.photos.push({ album.photos.push({
id: photoRecord["id"], id: photoRecord["id"],
photo: { photo: {

View File

@@ -1,6 +1,8 @@
import { createDirectusConnection } from "@/lib/directus"; import { createDirectusConnection } from "@/lib/directus";
import { print } from "graphql"; import { print } from "graphql";
import getCategories from '@/graphql/photos/getCategories.graphql'; import getCategories from "@/graphql/photos/getCategories.graphql";
import getCategory from "@/graphql/photos/getCategory.graphql";
import { getImageSize, getImageUrl } from "@/lib/images";
export async function getAllCategories(settings: GlobalSettings): Promise<PhotoAlbumCategory[]> { export async function getAllCategories(settings: GlobalSettings): Promise<PhotoAlbumCategory[]> {
const client = await createDirectusConnection(); const client = await createDirectusConnection();
@@ -9,17 +11,46 @@ export async function getAllCategories(settings: GlobalSettings): Promise<PhotoA
let categories: PhotoAlbumCategory[] = []; let categories: PhotoAlbumCategory[] = [];
result["Photo_Categories"].forEach((photoCategoryRecord: any) => { result["Photo_Categories"].forEach((photoCategoryRecord: any) => {
const imageSize = getImageSize(photoCategoryRecord["thumbnail"]["width"],
photoCategoryRecord["thumbnail"]["height"], 1.5);
categories.push({ categories.push({
id: photoCategoryRecord["id"], id: photoCategoryRecord["id"],
title: photoCategoryRecord["title"], title: photoCategoryRecord["title"],
url: photoCategoryRecord["url"], url: photoCategoryRecord["url"],
thumbnail: { thumbnail: {
url: photoCategoryRecord["thumbnail"]["filename_disk"], url: getImageUrl(photoCategoryRecord["thumbnail"]["filename_disk"]),
width: photoCategoryRecord["thumbnail"]["width"], width: imageSize.width,
height: photoCategoryRecord["thumbnail"]["height"] height: imageSize.height
} }
}); });
}); });
return categories; 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: getImageUrl(item["thumbnail"]["filename_disk"]),
width: imageSize.width,
height: imageSize.height
}
};
return categories;
}

View File

@@ -1,17 +1,22 @@
import { createDirectusConnection } from "@/lib/directus"; import { createDirectusConnection } from "@/lib/directus";
import { print } from "graphql"; import { print } from "graphql";
import getPhotos from '@/graphql/photos/getPhotos.graphql'; import getPhotos from "@/graphql/photos/getPhotos.graphql";
import md5 from "md5"; import md5 from "md5";
export async function getPhotoFromHash(albumUrl: string, hash: string): Promise<PhotoAlbumPhoto | null> { export async function getPhotoFromHash(albumUrl: string, hash: string): Promise<PhotoAlbumItem | null> {
const client = await createDirectusConnection(); const client = await createDirectusConnection();
const result = await client.query(print(getPhotos), { const result = await client.query(print(getPhotos), {
albumUrl: albumUrl albumUrl: albumUrl
}); });
let object: PhotoAlbumPhoto | null = null; let object: PhotoAlbumItem | null = null;
result["Photo_Albums"][0]["photos"].forEach((photo: any) => { 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({ const hashObject = md5(JSON.stringify({
id: photo.id, id: photo.id,
url: photo.photo.filename_disk, url: photo.photo.filename_disk,
@@ -24,9 +29,13 @@ export async function getPhotoFromHash(albumUrl: string, hash: string): Promise<
id: photo.id, id: photo.id,
text: photo.text, text: photo.text,
photo: { photo: {
url: photo.photo.url, url: photo.photo.filename_disk,
width: photo.photo.width, width: photo.photo.width,
height: photo.photo.height height: photo.photo.height
},
album: {
url: result["Photo_Albums"][0].url,
title: result["Photo_Albums"][0].title
} }
} }
} }

View File

@@ -1,9 +1,11 @@
import { formatDate } from "@/lib/dates"; import { formatDate } from "@/lib/dates";
import { createDirectusConnection } from "@/lib/directus"; import { createDirectusConnection } from "@/lib/directus";
import { print } from "graphql"; import { print } from "graphql";
import getProjects from '@/graphql/projects/getProjects.graphql'; import getProjects from "@/graphql/projects/getProjects.graphql";
import getProjectPost from '@/graphql/projects/getProject.graphql'; import getProjectPost from "@/graphql/projects/getProject.graphql";
import getLastProjectsQuery from '@/graphql/projects/getLastProjects.graphql'; import getLastProjectsQuery from "@/graphql/projects/getLastProjects.graphql";
import { getImageSize, getImageUrl } from "@/lib/images";
import { getImage } from "astro:assets";
export async function getAllProjects(settings: GlobalSettings): Promise<ProjectPost[]> { export async function getAllProjects(settings: GlobalSettings): Promise<ProjectPost[]> {
const client = await createDirectusConnection(); const client = await createDirectusConnection();
@@ -24,6 +26,9 @@ export async function getAllProjects(settings: GlobalSettings): Promise<ProjectP
projectRecord["search_engine"][0]["thumbnail"]["created_on"] 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 = { const project: ProjectPost = {
exists: true, exists: true,
type: "ProjectPost", type: "ProjectPost",
@@ -33,6 +38,11 @@ export async function getAllProjects(settings: GlobalSettings): Promise<ProjectP
content: projectRecord["content"], content: projectRecord["content"],
date: projectRecord["date"], date: projectRecord["date"],
url: projectRecord["url"], url: projectRecord["url"],
thumbnail: {
url: getImageUrl(projectRecord["search_engine"][0]["thumbnail"]["filename_disk"]),
width: projectRecord["search_engine"][0]["thumbnail"]["width"],
height: projectRecord["search_engine"][0]["thumbnail"]["height"]
},
searchEngine: { searchEngine: {
title: projectRecord["search_engine"][0]["title"], title: projectRecord["search_engine"][0]["title"],
description: projectRecord["search_engine"][0]["description"], description: projectRecord["search_engine"][0]["description"],
@@ -40,9 +50,9 @@ export async function getAllProjects(settings: GlobalSettings): Promise<ProjectP
canonical: projectRecord["search_engine"][0]["canonical"], canonical: projectRecord["search_engine"][0]["canonical"],
priority: projectRecord["search_engine"][0]["priority"], priority: projectRecord["search_engine"][0]["priority"],
thumbnail: { thumbnail: {
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"], url: getImageUrl(projectRecord["search_engine"][0]["thumbnail"]["filename_disk"]),
height: projectRecord["search_engine"][0]["thumbnail"]["height"], width: projectThumbnailImage.width,
width: projectRecord["search_engine"][0]["thumbnail"]["width"] height: projectThumbnailImage.height
} }
}, },
tags: [] tags: []
@@ -94,6 +104,16 @@ export async function getProject(settings: GlobalSettings, route: string): Promi
projectRecord["search_engine"][0]["thumbnail"]["created_on"] 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 thumbnail = await getImage({
src: getImageUrl(projectRecord["search_engine"][0]["thumbnail"]["filename_disk"]),
width: projectThumbnailImage.width,
height: projectThumbnailImage.height,
format: "jpeg"
});
const project: ProjectPost = { const project: ProjectPost = {
type: "ProjectPost", type: "ProjectPost",
exists: true, exists: true,
@@ -104,6 +124,11 @@ export async function getProject(settings: GlobalSettings, route: string): Promi
content: projectRecord["content"], content: projectRecord["content"],
date: projectRecord["date"], date: projectRecord["date"],
url: projectRecord["url"], url: projectRecord["url"],
thumbnail: {
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"],
width: projectRecord["search_engine"][0]["thumbnail"]["width"],
height: projectRecord["search_engine"][0]["thumbnail"]["height"]
},
searchEngine: { searchEngine: {
title: projectRecord["search_engine"][0]["title"], title: projectRecord["search_engine"][0]["title"],
description: projectRecord["search_engine"][0]["description"], description: projectRecord["search_engine"][0]["description"],
@@ -111,9 +136,9 @@ export async function getProject(settings: GlobalSettings, route: string): Promi
canonical: projectRecord["search_engine"][0]["canonical"], canonical: projectRecord["search_engine"][0]["canonical"],
priority: projectRecord["search_engine"][0]["priority"], priority: projectRecord["search_engine"][0]["priority"],
thumbnail: { thumbnail: {
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"], url: `${settings.website.domainName}${thumbnail.src}`,
height: projectRecord["search_engine"][0]["thumbnail"]["height"], width: projectThumbnailImage.width,
width: projectRecord["search_engine"][0]["thumbnail"]["width"] height: projectThumbnailImage.height
} }
}, },
tags: [] tags: []
@@ -162,6 +187,9 @@ export async function getLastProjects(amount: number): Promise<ProjectPost[]> {
projectRecord["search_engine"][0]["thumbnail"]["created_on"] 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 = { const project: ProjectPost = {
exists: true, exists: true,
type: "ProjectPost", type: "ProjectPost",
@@ -171,6 +199,11 @@ export async function getLastProjects(amount: number): Promise<ProjectPost[]> {
content: projectRecord["content"], content: projectRecord["content"],
date: projectRecord["date"], date: projectRecord["date"],
url: projectRecord["url"], url: projectRecord["url"],
thumbnail: {
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"],
width: projectRecord["search_engine"][0]["thumbnail"]["width"],
height: projectRecord["search_engine"][0]["thumbnail"]["height"]
},
searchEngine: { searchEngine: {
title: projectRecord["search_engine"][0]["title"], title: projectRecord["search_engine"][0]["title"],
description: projectRecord["search_engine"][0]["description"], description: projectRecord["search_engine"][0]["description"],
@@ -179,8 +212,8 @@ export async function getLastProjects(amount: number): Promise<ProjectPost[]> {
priority: projectRecord["search_engine"][0]["priority"], priority: projectRecord["search_engine"][0]["priority"],
thumbnail: { thumbnail: {
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"], url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"],
height: projectRecord["search_engine"][0]["thumbnail"]["height"], width: projectThumbnailImage.width,
width: projectRecord["search_engine"][0]["thumbnail"]["width"] height: projectThumbnailImage.height
} }
}, },
tags: [] tags: []
@@ -235,6 +268,9 @@ export async function getAllPaginatedProjects(settings: GlobalSettings, page: nu
projectRecord["search_engine"][0]["thumbnail"]["created_on"] 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 = { const project: ProjectPost = {
exists: true, exists: true,
type: "ProjectPost", type: "ProjectPost",
@@ -244,6 +280,11 @@ export async function getAllPaginatedProjects(settings: GlobalSettings, page: nu
content: projectRecord["content"], content: projectRecord["content"],
date: projectRecord["date"], date: projectRecord["date"],
url: projectRecord["url"], url: projectRecord["url"],
thumbnail: {
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"],
width: projectRecord["search_engine"][0]["thumbnail"]["width"],
height: projectRecord["search_engine"][0]["thumbnail"]["height"]
},
searchEngine: { searchEngine: {
title: projectRecord["search_engine"][0]["title"], title: projectRecord["search_engine"][0]["title"],
description: projectRecord["search_engine"][0]["description"], description: projectRecord["search_engine"][0]["description"],
@@ -252,8 +293,8 @@ export async function getAllPaginatedProjects(settings: GlobalSettings, page: nu
priority: projectRecord["search_engine"][0]["priority"], priority: projectRecord["search_engine"][0]["priority"],
thumbnail: { thumbnail: {
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"], url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"],
height: projectRecord["search_engine"][0]["thumbnail"]["height"], width: projectThumbnailImage.width,
width: projectRecord["search_engine"][0]["thumbnail"]["width"] height: projectThumbnailImage.height
} }
}, },
tags: [] tags: []

View File

@@ -1,6 +1,6 @@
import { createDirectusConnection } from "@/lib/directus"; import { createDirectusConnection } from "@/lib/directus";
import { print } from 'graphql'; import { print } from "graphql";
import getRobotsQuery from '@/graphql/settings/robots.graphql'; import getRobotsQuery from "@/graphql/settings/robots.graphql";
export async function getRobotsSettings(): Promise<RobotsSettings> { export async function getRobotsSettings(): Promise<RobotsSettings> {
const client = await createDirectusConnection(); const client = await createDirectusConnection();

View File

@@ -1,6 +1,6 @@
import { print } from 'graphql'; import { print } from "graphql";
import { createDirectusConnection } from "@/lib/directus"; import { createDirectusConnection } from "@/lib/directus";
import getSettingsQuery from '@/graphql/settings/settings.graphql'; import getSettingsQuery from "@/graphql/settings/settings.graphql";
export async function getSettings(): Promise<GlobalSettings> { export async function getSettings(): Promise<GlobalSettings> {
const client = await createDirectusConnection(); const client = await createDirectusConnection();

View 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
}
}
}

View 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
}
}
}
}
}

View 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
}
}
}

View 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>

View 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>

View 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>

View 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>
)
}

View File

@@ -1,12 +1,14 @@
--- ---
import '@/styles/global.css'; import "@/styles/global.css";
import { getSettings } from "@/content/settings/settings"; import { getSettings } from "@/content/settings/settings";
import { getTextColor } from '@/lib/colors'; import { getTextColor } from "@/lib/colors";
import Footer from "@/components/footer/Footer.astro";
interface Props { interface Props {
settings: BlogLayoutProps; settings: BlogLayoutProps;
} }
const tags = Astro.props.settings.tags ?? [];
const pageSettings = Astro.props.settings.searchEngine; const pageSettings = Astro.props.settings.searchEngine;
const settings = await getSettings(); const settings = await getSettings();
@@ -41,7 +43,7 @@ const css = {
<link rel="alternate" type="application/rss+xml" href="/rss.xml" title="RSS" /> <link rel="alternate" type="application/rss+xml" href="/rss.xml" title="RSS" />
<link rel="canonical" href={`${settings.website.domainName}/`} /> <link rel="canonical" href={`${settings.website.domainName}/`} />
<meta name="robots" content="index,follow" /> <meta name="robots" content="index,follow" />
<meta name="keywords" content={[].join(',')} /> <meta name="keywords" content={tags.join(",")} />
<!-- Low Priority Page Metadata --> <!-- Low Priority Page Metadata -->
<meta name="description" content={pageSettings.description} /> <meta name="description" content={pageSettings.description} />
@@ -54,7 +56,7 @@ const css = {
<meta property="og:url" content={`${settings.website.domainName}${Astro.url.pathname}`} /> <meta property="og:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
<meta property="og:site_name" content={settings.website.applicationName} /> <meta property="og:site_name" content={settings.website.applicationName} />
<meta property="article:author" content={settings.website.author.name} /> <meta property="article:author" content={settings.website.author.name} />
<meta property="article:tags" content={[].join(',')} /> <meta property="article:tags" content={[].join(",")} />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} /> <meta name="twitter:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} />
@@ -77,7 +79,10 @@ const css = {
<!-- Scripts and Style --> <!-- Scripts and Style -->
</head> </head>
<body style={ css } class="bg-[#fcfcfc]">
<slot name="content" /> <body style={ css } class="bg-[#fcfcfc] flex flex-col min-h-screen">
<slot class="grow" name="content" />
<Footer />
</body> </body>
</html> </html>

View File

@@ -0,0 +1,98 @@
---
import '@/styles/global.css';
import { getSettings } from "@/content/settings/settings";
import { getTextColor } from '@/lib/colors';
import { getImageSize } from '@/lib/images';
import { getImage } from 'astro:assets';
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)
};
const searchengine = Astro.props.settings.searchEngine;
// Changing the thumbnail here is okay, as we now have two images: a new thumbnail, and the original image.
// Might have to make it prettier down the line?
// TODO: See comment above.
const resizedThumbnail = getImageSize(searchengine.thumbnail.width,
searchengine.thumbnail.height, 0.756);
const thumbnail = await getImage({
src: searchengine.thumbnail.url,
width: resizedThumbnail.width,
height: resizedThumbnail.height,
format: "jpeg"
});
---
<!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={`${settings.website.domainName}${thumbnail.src}`} />
<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={`${settings.website.domainName}${thumbnail.src}`} />
<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>

View File

@@ -1,7 +1,8 @@
--- ---
import '@/styles/global.css'; import "@/styles/global.css";
import { getSettings } from "@/content/settings/settings"; import { getSettings } from "@/content/settings/settings";
import { getTextColor } from '@/lib/colors'; import { getTextColor } from "@/lib/colors";
import Footer from "@/components/footer/Footer.astro";
interface Props { interface Props {
settings: BlogLayoutProps; settings: BlogLayoutProps;
@@ -41,7 +42,7 @@ const css = {
<link rel="alternate" type="application/rss+xml" href="/rss.xml" title="RSS" /> <link rel="alternate" type="application/rss+xml" href="/rss.xml" title="RSS" />
<link rel="canonical" href={`${settings.website.domainName}/`} /> <link rel="canonical" href={`${settings.website.domainName}/`} />
<meta name="robots" content="index,follow" /> <meta name="robots" content="index,follow" />
<meta name="keywords" content={[].join(',')} /> <meta name="keywords" content={[].join(",")} />
<!-- Low Priority Page Metadata --> <!-- Low Priority Page Metadata -->
<meta name="description" content={pageSettings.description} /> <meta name="description" content={pageSettings.description} />
@@ -54,7 +55,7 @@ const css = {
<meta property="og:url" content={`${settings.website.domainName}${Astro.url.pathname}`} /> <meta property="og:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
<meta property="og:site_name" content={settings.website.applicationName} /> <meta property="og:site_name" content={settings.website.applicationName} />
<meta property="article:author" content={settings.website.author.name} /> <meta property="article:author" content={settings.website.author.name} />
<meta property="article:tags" content={[].join(',')} /> <meta property="article:tags" content={[].join(",")} />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} /> <meta name="twitter:title" content={settings.website.titleTemplate.replaceAll("%T", pageSettings.title)} />
@@ -77,7 +78,10 @@ const css = {
<!-- Scripts and Style --> <!-- Scripts and Style -->
</head> </head>
<body style={ css } class="bg-[#fcfcfc]">
<slot name="content" /> <body style={ css } class="bg-[#fcfcfc] flex flex-col min-h-screen">
<slot class="grow" name="content" />
<Footer />
</body> </body>
</html> </html>

View File

@@ -1,7 +1,8 @@
--- ---
import '@/styles/global.css'; import "@/styles/global.css";
import { getSettings } from "@/content/settings/settings"; import { getSettings } from "@/content/settings/settings";
import { getTextColor } from '@/lib/colors'; import { getTextColor } from "@/lib/colors";
import Footer from "@/components/footer/Footer.astro";
interface Props { interface Props {
settings: WebpageLayoutProps; settings: WebpageLayoutProps;
@@ -74,7 +75,10 @@ const css = {
<!-- Scripts and Style --> <!-- Scripts and Style -->
</head> </head>
<body style={ css } class="bg-[#fcfcfc]">
<slot name="content" /> <body style={ css } class="bg-[#fcfcfc] flex flex-col min-h-screen">
<slot class="grow" name="content" />
<Footer />
</body> </body>
</html> </html>

View File

@@ -1,6 +1,6 @@
export function getTextColor(bgColor: string) { export function getTextColor(bgColor: string) {
// Remove # if present // Remove # if present
const hex = bgColor.replace('#', ''); const hex = bgColor.replace("#", "");
// Convert hex to RGB // Convert hex to RGB
const r = parseInt(hex.substring(0, 2), 16) / 255; const r = parseInt(hex.substring(0, 2), 16) / 255;
@@ -19,5 +19,5 @@ export function getTextColor(bgColor: string) {
const luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B; const luminance = 0.2126 * R + 0.7152 * G + 0.0722 * B;
// Return white for dark backgrounds, black for light backgrounds // Return white for dark backgrounds, black for light backgrounds
return luminance > 0.179 ? '#000000' : '#fcfcfc'; return luminance > 0.179 ? "#000000" : "#fcfcfc";
} }

View File

@@ -1,6 +1,6 @@
export function formatDate(date: Date, format: string) { export function formatDate(date: Date, format: string) {
return format return format
.replaceAll("%Y", date.getFullYear().toString()) .replaceAll("%Y", date.getFullYear().toString())
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0')) .replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, "0"))
.replaceAll("%D", date.getDate().toString().padStart(2, '0')); .replaceAll("%D", date.getDate().toString().padStart(2, "0"));
} }

View File

@@ -1,3 +1,22 @@
export function getImageUrl(url: string) { export function getImageUrl(url: string) {
return `${import.meta.env.DIRECTUS_URL}assets/${url}`; 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)
}
}

View File

@@ -1,8 +1,11 @@
import { getBlog } from "@/content/blogs/blogs"; import { getBlog } from "@/content/blogs/blogs";
import { getWebpage } from "@/content/pages/pages"; import { getWebpage } from "@/content/pages/pages";
import { getAlbum } from "@/content/photos/albums"; import { getAlbum } from "@/content/photos/albums";
import { getAllCategories, getPhotoCategory } from "@/content/photos/categories";
import { getPhotoFromHash } from "@/content/photos/photos"; import { getPhotoFromHash } from "@/content/photos/photos";
import { getProject } from "@/content/projects/projects"; import { getProject } from "@/content/projects/projects";
import { getImageSize, getImageUrl } from "./images";
import { getImage } from "astro:assets";
export async function getPage(settings: GlobalSettings, route: string): Promise<PageType | null> { export async function getPage(settings: GlobalSettings, route: string): Promise<PageType | null> {
// Blog Index // Blog Index
@@ -91,10 +94,32 @@ export async function getPage(settings: GlobalSettings, route: string): Promise<
} }
// Photo Category Index // Photo Category Index
else if (regexToRoute({ template: settings.photo.categoryIndex.indexRouteTemplate, allowPagination: false }).regex.test(route)) { else if (regexToRoute({ template: settings.photo.categoryIndex.indexRouteTemplate, allowPagination: false }).regex.test(route)) {
const allCategories = await getAllCategories(settings);
const lastCategory = allCategories[0];
// Changing the thumbnail here is okay, as it gets everything again in the CategoryIndex.astro file.
// Might have to make it prettier down the line?
// TODO: See comment above.
const resizedThumbnail = getImageSize(lastCategory.thumbnail.width, lastCategory.thumbnail.height, 0.756);
const thumbnail = await getImage({
src: lastCategory.thumbnail.url,
width: resizedThumbnail.width,
height: resizedThumbnail.height,
format: "jpeg"
});
lastCategory.thumbnail = {
url: `${settings.website.domainName}${thumbnail.src}`,
width: resizedThumbnail.width,
height: resizedThumbnail.height
}
return { return {
route: route, route: route,
pageType: "PhotoCategoryIndex", pageType: "PhotoCategoryIndex",
page: { page: {
category: lastCategory,
type: "PhotoCategoryIndex", type: "PhotoCategoryIndex",
exists: true exists: true
} }
@@ -112,13 +137,33 @@ export async function getPage(settings: GlobalSettings, route: string): Promise<
params[key] = match[i + 1]; params[key] = match[i + 1];
}); });
const category = await getPhotoCategory(`/${params["C"]}`);
// Changing the thumbnail here is okay, as it gets everything again in the Category.astro file.
// Might have to make it prettier down the line?
// TODO: See comment above.
const resizedThumbnail = getImageSize(category.thumbnail.width, category.thumbnail.height, 0.756);
const thumbnail = await getImage({
src: category.thumbnail.url,
width: resizedThumbnail.width,
height: resizedThumbnail.height,
format: "jpeg"
});
category.thumbnail = {
url: `${settings.website.domainName}${thumbnail.src}`,
width: resizedThumbnail.width,
height: resizedThumbnail.height
};
return { return {
route: route, route: route,
pageType: "PhotoCategory", pageType: "PhotoCategory",
page: { page: {
type: "PhotoCategory", type: "PhotoCategory",
exists: true, exists: true,
category: params["C"], category: category,
pageNumber: params["page"] !== undefined ? Number(params["page"]) : 1 pageNumber: params["page"] !== undefined ? Number(params["page"]) : 1
} }
}; };
@@ -137,10 +182,30 @@ export async function getPage(settings: GlobalSettings, route: string): Promise<
const photoAlbum = await getAlbum(settings, `/${params["R"]}`); const photoAlbum = await getAlbum(settings, `/${params["R"]}`);
// Changing the thumbnail here is okay, as the thumbnail is unused in the Album.astro file.
// Might have to make it prettier down the line?
// TODO: See comment above.
const resizedThumbnail = getImageSize(photoAlbum.thumbnail.width, photoAlbum.thumbnail.height, 0.756);
const thumbnail = await getImage({
src: getImageUrl(photoAlbum.thumbnail.url),
width: resizedThumbnail.width,
height: resizedThumbnail.height,
format: "jpeg"
});
return { return {
route: route, route: route,
pageType: "PhotoAlbum", pageType: "PhotoAlbum",
page: photoAlbum page: {
...photoAlbum,
thumbnail: {
url: `${settings.website.domainName}${thumbnail.src}`,
width: resizedThumbnail.width,
height: resizedThumbnail.height
},
pageNumber: params["page"] !== undefined ? Number(params["page"]) : 1
}
}; };
} }
// Photograph // Photograph
@@ -168,7 +233,8 @@ export async function getPage(settings: GlobalSettings, route: string): Promise<
id: photo!.id, id: photo!.id,
photo: photo!.photo, photo: photo!.photo,
text: photo!.text text: photo!.text,
album: photo!.album
} }
}; };
} }
@@ -188,6 +254,16 @@ export async function getPage(settings: GlobalSettings, route: string): Promise<
} }
} }
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 { return {
route: route, route: route,
pageType: "Webpage", pageType: "Webpage",
@@ -197,7 +273,14 @@ export async function getPage(settings: GlobalSettings, route: string): Promise<
id: webpageContent.id, id: webpageContent.id,
lastModified: webpageContent.lastModified, lastModified: webpageContent.lastModified,
url: webpageContent.url, url: webpageContent.url,
searchEngine: webpageContent.searchEngine, searchEngine: {
...webpageContent.searchEngine,
thumbnail: {
url: `${settings.website.domainName}${thumbnail.src}`,
width: resizedImage.width,
height: resizedImage.height
}
},
components: webpageContent.components components: webpageContent.components
} }
}; };

View File

@@ -2,7 +2,7 @@ import { getAllBlogs } from "@/content/blogs/blogs";
import { getAllWebpages } from "@/content/pages/pages"; import { getAllWebpages } from "@/content/pages/pages";
import { getAllAlbums } from "@/content/photos/albums"; import { getAllAlbums } from "@/content/photos/albums";
import { getAllProjects } from "@/content/projects/projects"; import { getAllProjects } from "@/content/projects/projects";
import { getPhotoHash } from "./hash"; import { getPhotoHash } from "@/lib/hash";
import { getAllCategories } from "@/content/photos/categories"; import { getAllCategories } from "@/content/photos/categories";
export async function getAllRoutesList(settings: GlobalSettings): Promise<string[]> { export async function getAllRoutesList(settings: GlobalSettings): Promise<string[]> {
@@ -11,7 +11,9 @@ export async function getAllRoutesList(settings: GlobalSettings): Promise<string
const webpages = await getAllWebpages(); const webpages = await getAllWebpages();
webpages.forEach((webpage) => { webpages.forEach((webpage) => {
if (webpage.exists) {
routes.push(webpage.url); routes.push(webpage.url);
}
}); });
if (settings.blog.enabled) { if (settings.blog.enabled) {
@@ -48,6 +50,8 @@ export async function getAllRoutesList(settings: GlobalSettings): Promise<string
} }
if (settings.photo.enabled) { if (settings.photo.enabled) {
const categories = await getAllCategories(settings); const categories = await getAllCategories(settings);
if (categories.length > 0) {
const galleries = await getAllAlbums(settings); const galleries = await getAllAlbums(settings);
routes.push(settings.photo.categoryIndex.indexRouteTemplate); routes.push(settings.photo.categoryIndex.indexRouteTemplate);
@@ -85,6 +89,7 @@ export async function getAllRoutesList(settings: GlobalSettings): Promise<string
}); });
}); });
} }
}
return routes; return routes;
} }
@@ -94,10 +99,10 @@ export function getBlogRoute(blogSettings: BlogSettings, blog: BlogPost) {
return blogSettings.blogRouteTemplate return blogSettings.blogRouteTemplate
.replaceAll("%Y", date.getFullYear().toString()) .replaceAll("%Y", date.getFullYear().toString())
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0')) .replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, "0"))
.replaceAll("%D", date.getDate().toString().padStart(2, '0')) .replaceAll("%D", date.getDate().toString().padStart(2, "0"))
.replaceAll("%R", blog.url) .replaceAll("%R", blog.url)
.replace(/\/+/g, '/'); .replace(/\/+/g, "/");
} }
export function getProjectRoute(projectSettings: ProjectSettings, project: ProjectPost) { export function getProjectRoute(projectSettings: ProjectSettings, project: ProjectPost) {
@@ -105,16 +110,16 @@ export function getProjectRoute(projectSettings: ProjectSettings, project: Proje
return projectSettings.projectRouteTemplate return projectSettings.projectRouteTemplate
.replaceAll("%Y", date.getFullYear().toString()) .replaceAll("%Y", date.getFullYear().toString())
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0')) .replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, "0"))
.replaceAll("%D", date.getDate().toString().padStart(2, '0')) .replaceAll("%D", date.getDate().toString().padStart(2, "0"))
.replaceAll("%R", project.url) .replaceAll("%R", project.url)
.replace(/\/+/g, '/'); .replace(/\/+/g, "/");
} }
export function getCategoryRoute(photoSettings: WebsitePhotoSettings, category: PhotoAlbumCategory) { export function getCategoryRoute(photoSettings: WebsitePhotoSettings, category: PhotoAlbumCategory) {
return photoSettings.category.routeTemplate return photoSettings.category.routeTemplate
.replaceAll("%C", category.url) .replaceAll("%C", category.url)
.replace(/\/+/g, '/'); .replace(/\/+/g, "/");
} }
export function getAlbumRoute(photoSettings: WebsitePhotoSettings, album: PhotoAlbum) { export function getAlbumRoute(photoSettings: WebsitePhotoSettings, album: PhotoAlbum) {
@@ -122,11 +127,11 @@ export function getAlbumRoute(photoSettings: WebsitePhotoSettings, album: PhotoA
return photoSettings.album.routeTemplate return photoSettings.album.routeTemplate
.replaceAll("%Y", date.getFullYear().toString()) .replaceAll("%Y", date.getFullYear().toString())
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0')) .replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, "0"))
.replaceAll("%D", date.getDate().toString().padStart(2, '0')) .replaceAll("%D", date.getDate().toString().padStart(2, "0"))
.replaceAll("%C", album.category.url) .replaceAll("%C", album.category.url)
.replaceAll("%R", album.url) .replaceAll("%R", album.url)
.replace(/\/+/g, '/'); .replace(/\/+/g, "/");
} }
export function getPhotoRoute(photoSettings: WebsitePhotoSettings, album: PhotoAlbum, photo: PhotoAlbumPhoto) { export function getPhotoRoute(photoSettings: WebsitePhotoSettings, album: PhotoAlbum, photo: PhotoAlbumPhoto) {
@@ -134,10 +139,10 @@ export function getPhotoRoute(photoSettings: WebsitePhotoSettings, album: PhotoA
return photoSettings.photo.routeTemplate return photoSettings.photo.routeTemplate
.replaceAll("%Y", date.getFullYear().toString()) .replaceAll("%Y", date.getFullYear().toString())
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0')) .replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, "0"))
.replaceAll("%D", date.getDate().toString().padStart(2, '0')) .replaceAll("%D", date.getDate().toString().padStart(2, "0"))
.replaceAll("%C", album.category.url) .replaceAll("%C", album.category.url)
.replaceAll("%R", album.url) .replaceAll("%R", album.url)
.replaceAll("%H", getPhotoHash(photo)) .replaceAll("%H", getPhotoHash(photo))
.replace(/\/+/g, '/'); .replace(/\/+/g, "/");
} }

View File

@@ -5,6 +5,7 @@ import { getSettings } from "@/content/settings/settings"
import WebpageLayout from "@/layouts/WebpageLayout.astro"; import WebpageLayout from "@/layouts/WebpageLayout.astro";
import BlogLayout from "@/layouts/BlogLayout.astro"; import BlogLayout from "@/layouts/BlogLayout.astro";
import ProjectLayout from "@/layouts/ProjectLayout.astro"; import ProjectLayout from "@/layouts/ProjectLayout.astro";
import PhotoLayout from "@/layouts/PhotoLayout.astro";
import BlogIndex from "@/components/blogs/BlogIndex.astro"; import BlogIndex from "@/components/blogs/BlogIndex.astro";
import ProjectIndex from "@/components/projects/ProjectIndex.astro"; import ProjectIndex from "@/components/projects/ProjectIndex.astro";
import Webpage from "@/components/webpage/Webpage.astro"; import Webpage from "@/components/webpage/Webpage.astro";
@@ -12,6 +13,9 @@ import BlogPost from "@/components/blogs/BlogPost.astro";
import ProjectPost from "@/components/projects/ProjectPost.astro"; import ProjectPost from "@/components/projects/ProjectPost.astro";
import CategoryIndex from "@/components/photos/CategoryIndex.astro"; import CategoryIndex from "@/components/photos/CategoryIndex.astro";
import Category from "@/components/photos/Category.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() { export async function getStaticPaths() {
const settings = await getSettings(); const settings = await getSettings();
@@ -30,8 +34,6 @@ const settings = await getSettings();
const pathName = Astro.url.pathname === "/" ? "/" : Astro.url.pathname.replace(/\/$/, ""); const pathName = Astro.url.pathname === "/" ? "/" : Astro.url.pathname.replace(/\/$/, "");
const page = await getPage(settings, pathName); const page = await getPage(settings, pathName);
console.log(pathName);
if (page === null || page.page === null || !page.page.exists) { if (page === null || page.page === null || !page.page.exists) {
return new Response("Page not found.", { return new Response("Page not found.", {
status: 404, status: 404,
@@ -42,18 +44,8 @@ if (page === null || page.page === null || !page.page.exists) {
{ page.page.type === "Webpage" && page.page.exists && ( { page.page.type === "Webpage" && page.page.exists && (
<WebpageLayout settings={{ <WebpageLayout settings={{
searchEngine: { searchEngine: page.page.searchEngine
title: page.page.searchEngine.title, }}>
description: page.page.searchEngine.description,
allowCrawlers: page.page.searchEngine.allowCrawlers,
canonical: page.page.searchEngine.canonical,
priority: page.page.searchEngine.priority,
thumbnail: {
url: page.page.searchEngine.thumbnail.url,
width: 1200,
height: 630
}
}}}>
<Fragment slot="content"> <Fragment slot="content">
<Webpage webpage={page.page.components} /> <Webpage webpage={page.page.components} />
</Fragment> </Fragment>
@@ -82,19 +74,8 @@ if (page === null || page.page === null || !page.page.exists) {
{ page.page.type === "BlogPost" && ( { page.page.type === "BlogPost" && (
<BlogLayout settings={{ <BlogLayout settings={{
searchEngine: { searchEngine: page.page.searchEngine,
title: page.page.searchEngine.title, tags: page.page.tags.map((tag) => tag.text)
description: page.page.searchEngine.description,
allowCrawlers: page.page.searchEngine.allowCrawlers,
canonical: page.page.searchEngine.canonical,
priority: page.page.searchEngine.priority,
thumbnail: {
url: page.page.searchEngine.thumbnail.url,
width: 1200,
height: 630
}
},
tags: []
}}> }}>
<Fragment slot="content"> <Fragment slot="content">
<BlogPost blog={page.page} /> <BlogPost blog={page.page} />
@@ -124,19 +105,8 @@ if (page === null || page.page === null || !page.page.exists) {
{ page.page.type === "ProjectPost" && ( { page.page.type === "ProjectPost" && (
<ProjectLayout settings={{ <ProjectLayout settings={{
searchEngine: { searchEngine: page.page.searchEngine,
title: "Projects", tags: page.page.tags.map((tag) => tag.text)
description: "",
allowCrawlers: true,
canonical: null,
priority: 65,
thumbnail: {
url: "",
width: 1200,
height: 630
}
},
tags: []
}}> }}>
<Fragment slot="content"> <Fragment slot="content">
<ProjectPost project={page.page} /> <ProjectPost project={page.page} />
@@ -147,16 +117,12 @@ if (page === null || page.page === null || !page.page.exists) {
{ page.pageType === "PhotoCategoryIndex" && ( { page.pageType === "PhotoCategoryIndex" && (
<WebpageLayout settings={{ <WebpageLayout settings={{
searchEngine: { searchEngine: {
title: "Projects", title: "Categories",
description: "", description: "See the photo categories on this page, where you can see the different types of photography I do.",
allowCrawlers: true, allowCrawlers: true,
canonical: null, canonical: null,
priority: 65, priority: 65,
thumbnail: { thumbnail: page.page.category.thumbnail
url: "",
width: 1200,
height: 630
}
}}}> }}}>
<Fragment slot="content"> <Fragment slot="content">
<CategoryIndex /> <CategoryIndex />
@@ -167,16 +133,12 @@ if (page === null || page.page === null || !page.page.exists) {
{ page.pageType === "PhotoCategory" && ( { page.pageType === "PhotoCategory" && (
<WebpageLayout settings={{ <WebpageLayout settings={{
searchEngine: { searchEngine: {
title: "Projects", title: page.page.category.title,
description: "", description: `See the photos in the ${page.page.category.title.toLowerCase()} category.`,
allowCrawlers: true, allowCrawlers: true,
canonical: null, canonical: null,
priority: 65, priority: 65,
thumbnail: { thumbnail: page.page.category.thumbnail
url: "",
width: 1200,
height: 630
}
}}}> }}}>
<Fragment slot="content"> <Fragment slot="content">
<Category category={page.page} /> <Category category={page.page} />
@@ -184,22 +146,38 @@ if (page === null || page.page === null || !page.page.exists) {
</WebpageLayout> </WebpageLayout>
) } ) }
{ page.pageType === "Photo" && ( { page.pageType === "PhotoAlbum" && (
<WebpageLayout settings={{ <WebpageLayout settings={{
searchEngine: { searchEngine: {
title: "Projects", title: page.page.title,
description: "", description: `See the photos in the ${page.page.category.title.toLowerCase()} category.`,
allowCrawlers: true,
canonical: null,
priority: 65,
thumbnail: page.page.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, allowCrawlers: true,
canonical: null, canonical: null,
priority: 65, priority: 65,
thumbnail: { thumbnail: {
url: "", url: getImageUrl(page.page.photo.url),
width: 1200, width: page.page.photo.width,
height: 630 height: page.page.photo.height
} }
}}}> }}}>
<Fragment slot="content"> <Fragment slot="content">
<div>Photo</div> <Photo page={page.page} />
</Fragment> </Fragment>
</WebpageLayout> </PhotoLayout>
) } ) }

View File

@@ -7,8 +7,8 @@ export const GET = (async () => {
const robots = await getRobotsSettings(); const robots = await getRobotsSettings();
let crawlers = [ let crawlers = [
{ id: 'google', name: 'Googlebot' }, { id: "google", name: "Googlebot" },
{ id: 'bing', name: "Bingbot" }, { id: "bing", name: "Bingbot" },
{ id: "slurp", name: "Slurp" }, { id: "slurp", name: "Slurp" },
{ id: "duckduckgo", name: "DuckDuckBot" }, { id: "duckduckgo", name: "DuckDuckBot" },
{ id: "baidu", name: "Baiduspider" }, { id: "baidu", name: "Baiduspider" },
@@ -53,7 +53,7 @@ export const GET = (async () => {
`User-agent: ${crawlerData!.name}\nDisallow: /` `User-agent: ${crawlerData!.name}\nDisallow: /`
} }
crawlerContent = crawlerContent + "\n\n\n" crawlerContent = crawlerContent + "\n\n"
}); });
return new Response(crawlerContent.trim(), { return new Response(crawlerContent.trim(), {

View File

@@ -39,7 +39,7 @@ export const GET = (async ({ params }) => {
<loc>${settings.website.domainName}${page.url}</loc> <loc>${settings.website.domainName}${page.url}</loc>
<lastmod>${page.lastModified.toISOString()}</lastmod> <lastmod>${page.lastModified.toISOString()}</lastmod>
</sitemap> </sitemap>
`).join('')} `).join("")}
</sitemapindex> </sitemapindex>
`; `;
@@ -63,7 +63,7 @@ export async function getStaticPaths() {
let items: any[] = []; let items: any[] = [];
for (let i = 0; i < pages; i++) { for (let i = 0; i < pages; i++) {
items.push({ params: { page: (i + 1) } }); items.push({ params: { page: (i + 1).toString() } });
} }
return items; return items;

View File

@@ -54,7 +54,7 @@ export const GET = (async () => {
<loc>${settings.website.domainName}${item.url}</loc> <loc>${settings.website.domainName}${item.url}</loc>
<lastmod>${item.lastModified.toISOString()}</lastmod> <lastmod>${item.lastModified.toISOString()}</lastmod>
</sitemap> </sitemap>
`).join('')} `).join("")}
</sitemapindex> </sitemapindex>
`; `;

View File

@@ -39,7 +39,7 @@ export const GET = (async ({ params }) => {
<loc>${settings.website.domainName}${page.url}</loc> <loc>${settings.website.domainName}${page.url}</loc>
<lastmod>${page.lastModified.toISOString()}</lastmod> <lastmod>${page.lastModified.toISOString()}</lastmod>
</sitemap> </sitemap>
`).join('')} `).join("")}
</sitemapindex> </sitemapindex>
`; `;
@@ -63,7 +63,7 @@ export async function getStaticPaths() {
let items: any[] = []; let items: any[] = [];
for (let i = 0; i < pages; i++) { for (let i = 0; i < pages; i++) {
items.push({ params: { page: (i + 1) } }); items.push({ params: { page: (i + 1).toString() } });
} }
return items; return items;

View File

@@ -54,7 +54,7 @@ export const GET = (async () => {
<loc>${settings.website.domainName}${item.url}</loc> <loc>${settings.website.domainName}${item.url}</loc>
<lastmod>${item.lastModified.toISOString()}</lastmod> <lastmod>${item.lastModified.toISOString()}</lastmod>
</sitemap> </sitemap>
`).join('')} `).join("")}
</sitemapindex> </sitemapindex>
`; `;

View File

@@ -90,7 +90,7 @@ export const GET = (async () => {
<loc>${settings.website.domainName}${item.url}</loc> <loc>${settings.website.domainName}${item.url}</loc>
<lastmod>${item.lastModified.toISOString()}</lastmod> <lastmod>${item.lastModified.toISOString()}</lastmod>
</sitemap> </sitemap>
`).join('')} `).join("")}
</sitemapindex> </sitemapindex>
`; `;

View File

@@ -17,10 +17,12 @@ export const GET = (async ({ params }) => {
let pages: SitemapPage[] = []; let pages: SitemapPage[] = [];
selectedPages.forEach((page) => { selectedPages.forEach((page) => {
if (page.exists) {
pages.push({ pages.push({
url: page.url, url: page.url,
lastModified: page.lastModified lastModified: page.lastModified
}); });
}
}); });
let sitemapContent = ` let sitemapContent = `
@@ -31,7 +33,7 @@ export const GET = (async ({ params }) => {
<loc>${settings.website.domainName}${page.url}</loc> <loc>${settings.website.domainName}${page.url}</loc>
<lastmod>${page.lastModified.toISOString()}</lastmod> <lastmod>${page.lastModified.toISOString()}</lastmod>
</sitemap> </sitemap>
`).join('')} `).join("")}
</sitemapindex> </sitemapindex>
`; `;
@@ -55,7 +57,7 @@ export async function getStaticPaths() {
let items: any[] = []; let items: any[] = [];
for (let i = 0; i < pages; i++) { for (let i = 0; i < pages; i++) {
items.push({ params: { page: (i + 1) } }); items.push({ params: { page: (i + 1).toString() } });
} }
return items; return items;

View File

@@ -28,7 +28,7 @@ export const GET = (async () => {
<loc>${settings.website.domainName}${item.url}</loc> <loc>${settings.website.domainName}${item.url}</loc>
<lastmod>${item.lastModified.toISOString()}</lastmod> <lastmod>${item.lastModified.toISOString()}</lastmod>
</sitemap> </sitemap>
`).join('')} `).join("")}
</sitemapindex> </sitemapindex>
`; `;

View File

@@ -39,7 +39,7 @@ export const GET = (async ({ params }) => {
<loc>${settings.website.domainName}${page.url}</loc> <loc>${settings.website.domainName}${page.url}</loc>
<lastmod>${page.lastModified.toISOString()}</lastmod> <lastmod>${page.lastModified.toISOString()}</lastmod>
</sitemap> </sitemap>
`).join('')} `).join("")}
</sitemapindex> </sitemapindex>
`; `;
@@ -63,7 +63,7 @@ export async function getStaticPaths() {
let items: any[] = []; let items: any[] = [];
for (let i = 0; i < pages; i++) { for (let i = 0; i < pages; i++) {
items.push({ params: { page: (i + 1) } }); items.push({ params: { page: (i + 1).toString() } });
} }
return items; return items;

View File

@@ -54,7 +54,7 @@ export const GET = (async () => {
<loc>${settings.website.domainName}${item.url}</loc> <loc>${settings.website.domainName}${item.url}</loc>
<lastmod>${item.lastModified.toISOString()}</lastmod> <lastmod>${item.lastModified.toISOString()}</lastmod>
</sitemap> </sitemap>
`).join('')} `).join("")}
</sitemapindex> </sitemapindex>
`; `;

View File

@@ -8,6 +8,8 @@ type BlogPost = {
date: string; date: string;
content: string; content: string;
thumbnail: PhotoProps;
tags: Tag[]; tags: Tag[];
searchEngine: SearchEngine; searchEngine: SearchEngine;

View File

@@ -3,3 +3,8 @@ type PhotoProps = {
width: number; width: number;
height: number; height: number;
} }
type ResizedImageResponse = {
width: number;
height: number;
}

44
astro/src/types/footers/footer.d.ts vendored Normal file
View File

@@ -0,0 +1,44 @@
import type { StringValidation } from "astro:schema";
type Footer = {
id: string;
title: string | null;
logo: PhotoProps | null;
copyright: string | null;
columns: FooterColumn[];
socials: FooterSocial[] | null;
secondaryLinks: FooterSecondaryLink[] | null;
lastModified: Date;
}
type FooterColumn = {
id: string;
title: string;
links: FooterColumnLink[];
}
type FooterSocial = {
id: string;
name: string;
url: string;
icon: PhotoProps;
}
type FooterSecondaryLink = {
id: string;
text: string;
url: string;
}
type FooterColumnLink = {
id: string;
text: string;
url: string;
}

20
astro/src/types/menu/menu.d.ts vendored Normal file
View File

@@ -0,0 +1,20 @@
type Menu = {
id: string;
items: MenuColumn[];
}
type MenuColumn = {
type: "Column";
id: string;
title: string;
links: MenuLink[];
}
type MenuLink = {
type: "Link";
id: string;
text: string;
url: string;
}

View File

@@ -41,6 +41,6 @@ type PageType =
| { pageType: "ProjectPost"; page: ProjectPost; route: string } | { pageType: "ProjectPost"; page: ProjectPost; route: string }
| { pageType: "PhotoCategoryIndex"; page: PhotoCategoryIndex; route: string } | { pageType: "PhotoCategoryIndex"; page: PhotoCategoryIndex; route: string }
| { pageType: "PhotoCategory"; page: PhotoCategory; route: string } | { pageType: "PhotoCategory"; page: PhotoCategory; route: string }
| { pageType: "PhotoAlbum"; page: PhotoAlbum; route: string } | { pageType: "PhotoAlbum"; page: PhotoAlbumPage; route: string }
| { pageType: "Photo"; page: PhotoPage; route: string } | { pageType: "Photo"; page: PhotoPage; route: string }
| { pageType: "Unknown"; page: null; route: string }; | { pageType: "Unknown"; page: null; route: string };

View File

@@ -2,6 +2,31 @@ type PhotoAlbum = {
type: "PhotoAlbum"; type: "PhotoAlbum";
exists: boolean; exists: boolean;
id: string;
title: string;
url: string;
description: string | null;
thumbnail: PhotoProps;
startDate: string;
endDate: string | null;
location: string | null;
category: PhotoAlbumCategory;
photos: PhotoAlbumPhoto[];
lastModified: Date;
}
type PhotoAlbumPage = {
type: "PhotoAlbum";
exists: boolean;
pageNumber: number;
id: string;
title: string; title: string;
url: string; url: string;
description: string | null; description: string | null;
@@ -35,6 +60,16 @@ type PhotoCategory = {
type: "PhotoCategory"; type: "PhotoCategory";
exists: boolean; exists: boolean;
category: string; category: PhotoAlbumCategory;
pageNumber: number; pageNumber: number;
} }
type PhotoAlbumItem = {
id: string;
photo: PhotoProps;
text: string | null;
album: {
url: string;
title: string;
};
}

View File

@@ -1,4 +1,6 @@
type PhotoCategoryIndex = { type PhotoCategoryIndex = {
type: "PhotoCategoryIndex"; type: "PhotoCategoryIndex";
exists: boolean; exists: boolean;
category: PhotoAlbumCategory;
} }

View File

@@ -5,4 +5,20 @@ type PhotoPage = {
id: string; id: string;
photo: PhotoProps; photo: PhotoProps;
text: string | null; text: string | null;
album: {
url: string;
title: string;
};
}
type PhotoAlbumItem = {
photo: PhotoProps;
url: string;
}
type PhotoAlbumGalleryItem = {
id: string;
photo: PhotoProps;
text: string | null;
url: string;
} }

View File

@@ -8,6 +8,8 @@ type ProjectPost = {
date: string; date: string;
content: string; content: string;
thumbnail: PhotoProps;
tags: Tag[]; tags: Tag[];
searchEngine: SearchEngine; searchEngine: SearchEngine;

View File

@@ -3974,6 +3974,52 @@
"searchable": true "searchable": true
} }
}, },
{
"collection": "Menu_Link",
"field": "Menu_Column_id",
"type": "uuid",
"schema": {
"name": "Menu_Column_id",
"table": "Menu_Link",
"data_type": "uuid",
"default_value": null,
"generation_expression": null,
"max_length": null,
"numeric_precision": null,
"numeric_scale": null,
"is_generated": false,
"is_nullable": true,
"is_unique": false,
"is_indexed": false,
"is_primary_key": false,
"has_auto_increment": false,
"foreign_key_schema": "public",
"foreign_key_table": "Menu_Column",
"foreign_key_column": "id",
"comment": null
},
"meta": {
"collection": "Menu_Link",
"field": "Menu_Column_id",
"special": null,
"interface": "select-dropdown-m2o",
"options": null,
"display": null,
"display_options": null,
"readonly": false,
"hidden": true,
"sort": 8,
"width": "full",
"translations": null,
"note": null,
"conditions": null,
"required": false,
"group": null,
"validation": null,
"validation_message": null,
"searchable": true
}
},
{ {
"collection": "Menu_items", "collection": "Menu_items",
"field": "id", "field": "id",
@@ -11823,7 +11869,7 @@
"display_options": null, "display_options": null,
"readonly": false, "readonly": false,
"hidden": true, "hidden": true,
"sort": 8, "sort": 7,
"width": "full", "width": "full",
"translations": null, "translations": null,
"note": null, "note": null,
@@ -11835,6 +11881,37 @@
"searchable": true "searchable": true
} }
}, },
{
"collection": "Menu_Column",
"field": "links",
"type": "alias",
"schema": null,
"meta": {
"collection": "Menu_Column",
"field": "links",
"special": [
"o2m"
],
"interface": "list-o2m",
"options": {
"template": "{{text}}"
},
"display": null,
"display_options": null,
"readonly": false,
"hidden": false,
"sort": 8,
"width": "full",
"translations": null,
"note": null,
"conditions": null,
"required": true,
"group": null,
"validation": null,
"validation_message": null,
"searchable": true
}
},
{ {
"collection": "Menu_Column", "collection": "Menu_Column",
"field": "sort", "field": "sort",
@@ -24005,6 +24082,32 @@
"one_deselect_action": "nullify" "one_deselect_action": "nullify"
} }
}, },
{
"collection": "Menu_Link",
"field": "Menu_Column_id",
"related_collection": "Menu_Column",
"schema": {
"constraint_name": "menu_link_menu_column_id_foreign",
"table": "Menu_Link",
"column": "Menu_Column_id",
"foreign_key_schema": "public",
"foreign_key_table": "Menu_Column",
"foreign_key_column": "id",
"on_update": "NO ACTION",
"on_delete": "SET NULL"
},
"meta": {
"many_collection": "Menu_Link",
"many_field": "Menu_Column_id",
"one_collection": "Menu_Column",
"one_field": "links",
"one_collection_field": null,
"one_allowed_collections": null,
"junction_field": null,
"sort_field": null,
"one_deselect_action": "nullify"
}
},
{ {
"collection": "Menu_items", "collection": "Menu_items",
"field": "Menu_id", "field": "Menu_id",