Compare commits
11 Commits
6f59613edf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24d01e44b0
|
||
|
|
6545a25741
|
||
|
|
c527a13c54
|
||
|
|
c3299868c0
|
||
|
|
71074a8b49
|
||
|
|
ea5c35ee85
|
||
| 6b8b032832 | |||
|
|
e314d6edcb
|
||
|
|
3263ab9123
|
||
|
|
207d5998da
|
||
|
|
31fe7e9a37
|
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -4,3 +4,6 @@
|
|||||||
*.woff2 filter=lfs diff=lfs merge=lfs -text
|
*.woff2 filter=lfs diff=lfs merge=lfs -text
|
||||||
*.ttf filter=lfs diff=lfs merge=lfs -text
|
*.ttf filter=lfs diff=lfs merge=lfs -text
|
||||||
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.JPEG filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.webm filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
server: {
|
server: {
|
||||||
watch: {
|
watch: {
|
||||||
// Customize watch behavior to reduce file watchers
|
|
||||||
ignored: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
|
ignored: ["**/node_modules/**", "**/dist/**", "**/.git/**"],
|
||||||
usePolling: process.env.NODE_ENV === "production",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
18
package.json
18
package.json
@@ -11,15 +11,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.5",
|
"@astrojs/check": "^0.9.5",
|
||||||
"@astrojs/mdx": "^4.3.7",
|
"@astrojs/mdx": "^4.3.9",
|
||||||
"@astrojs/svelte": "^7.2.0",
|
"@astrojs/svelte": "^7.2.1",
|
||||||
"@astrojs/tailwind": "^6.0.2",
|
"@astrojs/tailwind": "^6.0.2",
|
||||||
"astro": "^5.14.8",
|
"astro": "^5.15.2",
|
||||||
"astro-i18n-aut": "^0.7.3",
|
"astro-i18n-aut": "^0.7.3",
|
||||||
"exifreader": "^4.32.0",
|
"exifreader": "^4.32.0",
|
||||||
"svelte": "^5.39.8",
|
"svelte": "^5.42.3",
|
||||||
"svelte-gestures": "^5.2.2",
|
"svelte-gestures": "^5.2.2",
|
||||||
"tailwindcss": "^4.1.14",
|
"tailwindcss": "^4.1.16",
|
||||||
"thumbhash": "^0.1.1",
|
"thumbhash": "^0.1.1",
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
@@ -27,16 +27,16 @@
|
|||||||
"@astrojs/sitemap": "^3.6.0",
|
"@astrojs/sitemap": "^3.6.0",
|
||||||
"@iconify-json/tabler": "^1.2.23",
|
"@iconify-json/tabler": "^1.2.23",
|
||||||
"@types/markdown-it": "^14.1.2",
|
"@types/markdown-it": "^14.1.2",
|
||||||
"@unocss/preset-icons": "^66.5.2",
|
"@unocss/preset-icons": "^66.5.4",
|
||||||
"@unocss/reset": "^66.5.2",
|
"@unocss/reset": "^66.5.4",
|
||||||
"astro-font": "^1.1.0",
|
"astro-font": "^1.1.0",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"ogl": "^1.0.11",
|
"ogl": "^1.0.11",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"sharp": "^0.34.4",
|
"sharp": "^0.34.4",
|
||||||
"unocss": "^66.5.2",
|
"unocss": "^66.5.4",
|
||||||
"unplugin-icons": "^22.4.2",
|
"unplugin-icons": "^22.5.0",
|
||||||
"vite-plugin-glsl": "^1.5.4"
|
"vite-plugin-glsl": "^1.5.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1273
pnpm-lock.yaml
generated
1273
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
43
src/components/ClientSearch.svelte
Normal file
43
src/components/ClientSearch.svelte
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
let searchTerm = "";
|
||||||
|
let staticContainer: HTMLElement | null;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
staticContainer = document.getElementById("resource-list-static");
|
||||||
|
});
|
||||||
|
|
||||||
|
function search() {
|
||||||
|
if (!staticContainer) return;
|
||||||
|
|
||||||
|
const lowerCaseSearchTerm = searchTerm.toLowerCase().trim();
|
||||||
|
|
||||||
|
if (!lowerCaseSearchTerm) {
|
||||||
|
[...staticContainer?.children].forEach((element: HTMLElement) => {
|
||||||
|
element.style.display = "";
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const element of staticContainer?.children) {
|
||||||
|
const el = element as HTMLElement;
|
||||||
|
const isMatch = el.dataset.searchTerm?.includes(lowerCaseSearchTerm);
|
||||||
|
if (isMatch) {
|
||||||
|
el.style.display = "";
|
||||||
|
} else {
|
||||||
|
el.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: search(searchTerm);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="search-wrapper my-4 noise relative">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
bind:value={searchTerm}
|
||||||
|
placeholder={`Search...`}
|
||||||
|
class="w-full p-2 gradient border-1 border-neutral rounded noise" />
|
||||||
|
</div>
|
||||||
@@ -25,10 +25,14 @@ const translatePath = useTranslatedPath(Astro.url);
|
|||||||
const t = useTranslations(Astro.url);
|
const t = useTranslations(Astro.url);
|
||||||
|
|
||||||
const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
||||||
|
|
||||||
|
const image = cover as unknown;
|
||||||
|
|
||||||
|
const hasCover = typeof image === "string" ? !!image?.length : !!cover?.src;
|
||||||
---
|
---
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
classes={`grid gradient border-1 border-neutral overflow-hidden ${cover ? "grid-rows-[200px_1fr] xs:grid-rows-none xs:grid-cols-[1fr_200px]" : ""}`}>
|
classes={`grid gradient border-1 border-neutral overflow-hidden ${hasCover ? "grid-rows-[200px_1fr] xs:grid-rows-none xs:grid-cols-[1fr_200px]" : ""}`}>
|
||||||
<Card.Content classes="px-8 py-7 order-last xs:order-first">
|
<Card.Content classes="px-8 py-7 order-last xs:order-first">
|
||||||
{
|
{
|
||||||
(date || body || rating !== undefined) && (
|
(date || body || rating !== undefined) && (
|
||||||
@@ -57,14 +61,15 @@ const link = translatePath(`/${collection}/${id.split("/")[0]}`);
|
|||||||
<Card.ReadMoreButton link={link} text={t("read-more")} />
|
<Card.ReadMoreButton link={link} text={t("read-more")} />
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
{
|
{
|
||||||
cover && (
|
hasCover && (
|
||||||
<a href={link}>
|
<a href={link}>
|
||||||
<Image
|
<Image
|
||||||
hash
|
hash
|
||||||
loader={false}
|
loader={false}
|
||||||
src={cover as ImageMetadata}
|
src={cover as ImageMetadata}
|
||||||
alt={"cover for " + title}
|
alt={"cover for " + title}
|
||||||
class="right-0 h-full object-cover object-center rounded-none border-l border-neutral"
|
class="h-full right-0 object-cover object-center rounded-none border-l border-neutral"
|
||||||
|
pictureClass="h-full"
|
||||||
thumbnail
|
thumbnail
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -15,23 +15,25 @@ interface Props {
|
|||||||
thumbnail?: boolean;
|
thumbnail?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkImage(image: ImageMetadata) {
|
async function checkImage(
|
||||||
|
image: ImageMetadata,
|
||||||
|
): Promise<{ height: number; width: number } | undefined> {
|
||||||
const src = typeof image === "string" ? image : image.src;
|
const src = typeof image === "string" ? image : image.src;
|
||||||
if (!src) return false;
|
if (!src) return;
|
||||||
try {
|
try {
|
||||||
if (src.startsWith("/@fs") || src.startsWith("/_astro")) return true;
|
if (src.startsWith("/@fs") || src.startsWith("/_astro")) return image;
|
||||||
const res = await inferRemoteSize(src);
|
const res = await inferRemoteSize(src);
|
||||||
if (res.format) {
|
if (res.format) {
|
||||||
image.format = res.format;
|
image.format = res.format;
|
||||||
return true;
|
return res;
|
||||||
} else {
|
} else {
|
||||||
console.log("Failed to load: ", src);
|
console.log("Failed to load: ", src);
|
||||||
}
|
}
|
||||||
return false;
|
return;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("\n");
|
console.log("\n");
|
||||||
console.log("Failed to fetch: ", src);
|
console.log("Failed to fetch: ", src);
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +72,7 @@ const definedSizes = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const sizes = thumbnail
|
const sizes = thumbnail
|
||||||
? [definedSizes[0]]
|
? [definedSizes[1]]
|
||||||
: definedSizes.filter((size) => !maxWidth || size.width <= maxWidth);
|
: definedSizes.filter((size) => !maxWidth || size.width <= maxWidth);
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -81,16 +83,19 @@ const sizes = thumbnail
|
|||||||
alt={alt}
|
alt={alt}
|
||||||
data-thumbhash={thumbhash}
|
data-thumbhash={thumbhash}
|
||||||
data-exif={JSON.stringify(exif)}
|
data-exif={JSON.stringify(exif)}
|
||||||
inferSize={true}
|
width={imageOk?.width}
|
||||||
|
height={imageOk?.height}
|
||||||
pictureAttributes={{
|
pictureAttributes={{
|
||||||
class: `${hash ? "block h-full relative" : ""} ${loader ? "thumb" : ""} ${pictureClass}`,
|
class: `${hash ? "block h-full relative" : ""} ${loader ? "thumb" : ""} ${pictureClass}`,
|
||||||
}}
|
}}
|
||||||
class={`${Astro.props.class} h-full w-full`}
|
class={`${Astro.props.class} w-full`}
|
||||||
widths={sizes.map((size) => size.width)}
|
widths={sizes.map((size) => size.width)}
|
||||||
sizes={sizes
|
sizes={sizes
|
||||||
.map((size) => `${size.media || "100vw"} ${size.width}px`)
|
.map((size) => `${size.media || "100vw"} ${size.width}px`)
|
||||||
.join(", ")}>
|
.join(", ")}>
|
||||||
<slot />
|
<slot />
|
||||||
</AstroImage>
|
</AstroImage>
|
||||||
) : undefined
|
) : (
|
||||||
|
<div>{JSON.stringify({ "imageOk":imageOk, image })}</div>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,14 @@
|
|||||||
let height: number;
|
let height: number;
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
|
||||||
function hide(img: HTMLPictureElement) {
|
function hide(index: number) {
|
||||||
|
const img = images[index];
|
||||||
img.classList.remove("active");
|
img.classList.remove("active");
|
||||||
}
|
}
|
||||||
|
|
||||||
function show(img: HTMLPictureElement) {
|
const heightCache = [];
|
||||||
|
function show(index: number) {
|
||||||
|
const img = images[index];
|
||||||
img.classList.add("active");
|
img.classList.add("active");
|
||||||
const _img = img.querySelector("img") || img;
|
const _img = img.querySelector("img") || img;
|
||||||
if (!_img) return;
|
if (!_img) return;
|
||||||
@@ -21,9 +24,12 @@
|
|||||||
_img.style.opacity = "1";
|
_img.style.opacity = "1";
|
||||||
});
|
});
|
||||||
altText = _img["alt"] ?? _img.getAttribute("alt") ?? "";
|
altText = _img["alt"] ?? _img.getAttribute("alt") ?? "";
|
||||||
height = _img.getBoundingClientRect().height;
|
if (heightCache[index]) {
|
||||||
|
height = heightCache[index];
|
||||||
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
height = _img.getBoundingClientRect().height;
|
height = heightCache[index] ?? _img.getBoundingClientRect().height;
|
||||||
|
heightCache[index] = height;
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,16 +37,16 @@
|
|||||||
function setIndex(i: number) {
|
function setIndex(i: number) {
|
||||||
if (i < 0) i = images.length - 1;
|
if (i < 0) i = images.length - 1;
|
||||||
if (i >= images.length) i = 0;
|
if (i >= images.length) i = 0;
|
||||||
hide(images[index]);
|
hide(index);
|
||||||
index = i;
|
index = i;
|
||||||
show(images[index]);
|
show(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (slot && !images?.length) {
|
$: if (slot && !images?.length) {
|
||||||
images = Array.from(slot.querySelectorAll("picture"));
|
images = Array.from(slot.querySelectorAll("picture"));
|
||||||
if (images?.length) {
|
if (images?.length) {
|
||||||
images.forEach(hide);
|
images.forEach((_, i) => hide(i));
|
||||||
show(images[index]);
|
show(index);
|
||||||
images[index].onload = () => {
|
images[index].onload = () => {
|
||||||
loaded = true;
|
loaded = true;
|
||||||
height = images[index].getBoundingClientRect().height;
|
height = images[index].getBoundingClientRect().height;
|
||||||
@@ -54,11 +60,10 @@
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="wrapper grid overflow-hidden rounded-xl border border-neutral"
|
class="wrapper grid overflow-hidden rounded-xl border border-neutral"
|
||||||
class:title
|
class:title={true}
|
||||||
class:not-loaded={!loaded}
|
class:not-loaded={!loaded}
|
||||||
class:loaded
|
class:loaded
|
||||||
style={`--height:${height}px`}>
|
style={`--height:${height}px`}>
|
||||||
{#if title}
|
|
||||||
<div class="flex items-center p-x-4 p-y-6 bg justify-between">
|
<div class="flex items-center p-x-4 p-y-6 bg justify-between">
|
||||||
<h3>{title}</h3>
|
<h3>{title}</h3>
|
||||||
|
|
||||||
@@ -74,9 +79,8 @@
|
|||||||
on:click={() => setIndex(index + 1)}></button>
|
on:click={() => setIndex(index + 1)}></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="images border-t-1 border-b-1 border-neutral" bind:this={slot}>
|
<div class="images border-block border-neutral" bind:this={slot}>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<div class="px-4 flex items-center place-content-between bg">
|
<div class="px-4 flex items-center place-content-between bg">
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
typeof d === "string" ? new Date(d) : d;
|
typeof d === "string" ? new Date(d) : d;
|
||||||
|
|
||||||
const iso = (d: string | Date) => {
|
const iso = (d: string | Date) => {
|
||||||
if(!d) return ""
|
if (!d) return "";
|
||||||
const v = toDate(d);
|
const v = toDate(d);
|
||||||
if(!v?.getTime) return ""
|
if (!v?.getTime) return "";
|
||||||
return isNaN(v.getTime()) ? "" : v.toISOString();
|
return isNaN(v.getTime()) ? "" : v.toISOString();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,7 +34,8 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex gap-3 wrapper">
|
{#if rating || date || readDuration || author}
|
||||||
|
<div class="flex flex-wrap gap-3 wrapper">
|
||||||
{#if rating}
|
{#if rating}
|
||||||
<div class="text-sm bg-light">{formatRating(rating)}</div>
|
<div class="text-sm bg-light">{formatRating(rating)}</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
<div class="text-sm bg-light">{author}</div>
|
<div class="text-sm bg-light">{author}</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.wrapper > * {
|
.wrapper > * {
|
||||||
@@ -57,5 +58,6 @@
|
|||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -6,6 +6,6 @@
|
|||||||
<a
|
<a
|
||||||
href={link}
|
href={link}
|
||||||
data-astro-prefetch
|
data-astro-prefetch
|
||||||
class="bg-light p-2 text-s rounded-md px-4 flex flex-0 items-center gap-2 w-fit"
|
class="mt-auto bg-light p-2 text-s rounded-md px-4 flex flex-0 items-center gap-2 w-fit"
|
||||||
>{text}<span class="i-tabler-arrow-right inline-block w-4 h-4"></span>
|
>{text}<span class="i-tabler-arrow-right inline-block w-4 h-4"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -13,12 +13,17 @@ const { resource } = Astro.props;
|
|||||||
resource?.content?.image && (
|
resource?.content?.image && (
|
||||||
<Image
|
<Image
|
||||||
hash
|
hash
|
||||||
src={{ src: memorium.getImageUrl(resource.content.image) } as ImageMetadata}
|
src={
|
||||||
|
{ src: memorium.getImageUrl(resource.content.image) } as ImageMetadata
|
||||||
|
}
|
||||||
alt="Cover for {resource?.content?.name}"
|
alt="Cover for {resource?.content?.name}"
|
||||||
class="rounded-2xl overflow-hidden"
|
class="rounded-2xl overflow-hidden"
|
||||||
pictureClass="rounded-2xl"
|
pictureClass="rounded-2xl box-shadow"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div set:html={markdownToHtml(resource?.content?.articleBody)} />
|
<div
|
||||||
|
class="flex flex-col gap-4"
|
||||||
|
set:html={markdownToHtml(resource?.content?.articleBody)}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -4,29 +4,44 @@ import { markdownToHtml } from "@helpers/markdown";
|
|||||||
import Image from "@components/Image.astro";
|
import Image from "@components/Image.astro";
|
||||||
import type { ImageMetadata } from "astro";
|
import type { ImageMetadata } from "astro";
|
||||||
|
|
||||||
const { resource } = Astro.props
|
const { resource } = Astro.props;
|
||||||
const ingredients = resource?.content?.recipeIngredient || [];
|
const ingredients = resource?.content?.recipeIngredient || [];
|
||||||
const instructions = resource?.content?.recipeInstructions || [];
|
const instructions = resource?.content?.recipeInstructions || [];
|
||||||
---
|
---
|
||||||
|
|
||||||
<h1 class="text-4xl">{resource?.content?.name}</h1>
|
<h1 class="text-4xl">{resource?.content?.name}</h1>
|
||||||
<div>
|
<div>
|
||||||
{resource?.content?.image && <Image hash src={{src: memorium.getImageUrl(resource.content.image)} as ImageMetadata} alt="Cover for {resource?.content?.name}" class="rounded-2xl overflow-hidden" pictureClass="rounded-2xl" />}
|
|
||||||
</div>
|
|
||||||
<p>{resource?.content?.description}</p>
|
|
||||||
<h2 class="text-2xl">Ingredients</h2>
|
|
||||||
<ul>
|
|
||||||
{
|
{
|
||||||
ingredients.map((ingredient: string) => (
|
resource?.content?.image && (
|
||||||
|
<Image
|
||||||
|
hash
|
||||||
|
src={
|
||||||
|
{ src: memorium.getImageUrl(resource.content.image) } as ImageMetadata
|
||||||
|
}
|
||||||
|
alt="Cover for {resource?.content?.name}"
|
||||||
|
class="rounded-2xl overflow-hidden"
|
||||||
|
pictureClass="rounded-2xl box-shadow"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex flex-col gap-4 px-4"
|
||||||
|
set:html={markdownToHtml(resource?.content?.description ?? "")}
|
||||||
|
/>
|
||||||
|
<h2 class="text-2xl px-4">Ingredients</h2>
|
||||||
|
<ul class="list-disc px-10">
|
||||||
|
{
|
||||||
|
ingredients.filter((s:string) => !!s?.length).map((ingredient: string) => (
|
||||||
<li set:html={markdownToHtml(ingredient)} />
|
<li set:html={markdownToHtml(ingredient)} />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2 class="text-2xl">Steps</h2>
|
<h2 class="text-2xl px-4">Steps</h2>
|
||||||
<ol>
|
<ol class="list-decimal px-10">
|
||||||
{
|
{
|
||||||
instructions.map((ingredient: string) => (
|
instructions.filter((s:string) => !!s?.length).map((ingredient: string) => (
|
||||||
<li set:html={markdownToHtml(ingredient)} />
|
<li set:html={markdownToHtml(ingredient)} />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function formatRating(rating: string | number) {
|
|||||||
}
|
}
|
||||||
alt="Cover for {resource?.content?.name}"
|
alt="Cover for {resource?.content?.name}"
|
||||||
class="rounded-2xl overflow-hidden"
|
class="rounded-2xl overflow-hidden"
|
||||||
pictureClass="rounded-2xl w-1/2 mr-4 mb-4 float-left"
|
pictureClass="rounded-2xl w-1/2 mr-4 mb-4 float-left box-shadow"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -23,26 +23,33 @@ export type MemoriumEntry = MemoriumFile | MemoriumDir;
|
|||||||
const SERVER_URL = "https://marka.max-richter.dev";
|
const SERVER_URL = "https://marka.max-richter.dev";
|
||||||
//const SERVER_URL = "http://localhost:8080";
|
//const SERVER_URL = "http://localhost:8080";
|
||||||
|
|
||||||
|
const cache = {};
|
||||||
|
|
||||||
export async function listResource(
|
export async function listResource(
|
||||||
id: string,
|
id: string,
|
||||||
): Promise<MemoriumEntry | undefined> {
|
): Promise<MemoriumEntry | undefined> {
|
||||||
const url = `${SERVER_URL}/resources/${id}`;
|
const url = `${SERVER_URL}/resources/${id}`;
|
||||||
|
if (cache[url]) return cache[url];
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
|
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.type == "dir") {
|
if (json.type == "dir") {
|
||||||
return {
|
const res = {
|
||||||
...json,
|
...json,
|
||||||
content: json.content.filter((res: MemoriumEntry) =>
|
content: json.content.filter((res: MemoriumEntry) =>
|
||||||
res.mime === "application/markdown"
|
res.mime === "application/markdown"
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
cache[url] = res;
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
cache[url] = json;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
console.log("Failed to get: ", url);
|
console.log("Failed to get: ", url);
|
||||||
|
cache[url] = undefined;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ p {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
h1,
|
h1,
|
||||||
h2,
|
h2,
|
||||||
h3 {
|
h3 {
|
||||||
@@ -103,14 +107,23 @@ picture.thumb-loading::before {
|
|||||||
header::before {
|
header::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 10px;
|
width: 20px;
|
||||||
height: 10px;
|
height: 20px;
|
||||||
right: 3rem;
|
right: 3rem;
|
||||||
bottom: -10px;
|
bottom: -20px;
|
||||||
transform: rotate(90deg);
|
background: var(--background-dark);
|
||||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 249 249' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M248.5 0H0V249C0.268799 111.435 111.423 0 248.5 0Z' fill='white'/%3E%3C/svg%3E");
|
mask-image: radial-gradient(circle at bottom left,
|
||||||
|
transparent 19.5px,
|
||||||
|
rgba(0, 0, 0, .5) 20px,
|
||||||
|
#000 20.5px);
|
||||||
|
box-shadow: 0px 0px 10px red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark header::before {
|
.box-shadow {
|
||||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 249 249' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M248.5 0H0V249C0.268799 111.435 111.423 0 248.5 0Z' fill='%2316161e'/%3E%3C/svg%3E");
|
box-shadow:
|
||||||
|
0px 1px 1px rgba(3, 7, 18, 0.02),
|
||||||
|
0px 5px 4px rgba(3, 7, 18, 0.03),
|
||||||
|
0px 12px 9px rgba(3, 7, 18, 0.05),
|
||||||
|
0px 20px 15px rgba(3, 7, 18, 0.06),
|
||||||
|
0px 32px 24px rgba(3, 7, 18, 0.08);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
import Layout from "@layouts/Layout.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<Layout title="Home"> Sup people :) </Layout>
|
|
||||||
@@ -3,7 +3,7 @@ import Layout from "@layouts/Layout.astro";
|
|||||||
import { useTranslatedPath } from "@i18n/utils";
|
import { useTranslatedPath } from "@i18n/utils";
|
||||||
import ResourceDisplay from "@components/resources/Display.astro";
|
import ResourceDisplay from "@components/resources/Display.astro";
|
||||||
import * as memorium from "@helpers/memorium";
|
import * as memorium from "@helpers/memorium";
|
||||||
import { resources as resourceTypes } from "../resources.ts";
|
import { resources as resourceTypes } from "@content/resources.ts";
|
||||||
|
|
||||||
const { resourceType, resourceName } = Astro?.params;
|
const { resourceType, resourceName } = Astro?.params;
|
||||||
|
|
||||||
@@ -14,7 +14,6 @@ export async function getStaticPaths() {
|
|||||||
const paths = await Promise.all(
|
const paths = await Promise.all(
|
||||||
resourceTypes.map(async (resourceType) => {
|
resourceTypes.map(async (resourceType) => {
|
||||||
const resources = await memorium.listResource(resourceType.id);
|
const resources = await memorium.listResource(resourceType.id);
|
||||||
console.log({resources:resources?.content[0]})
|
|
||||||
return resources?.content?.map((res: any) => {
|
return resources?.content?.map((res: any) => {
|
||||||
return {
|
return {
|
||||||
params: {
|
params: {
|
||||||
@@ -48,7 +47,7 @@ const resource = await memorium.listResource(
|
|||||||
{
|
{
|
||||||
resource?.content?.url && (
|
resource?.content?.url && (
|
||||||
<a class="flex gap-1 items-center" href={resource?.content?.url}>
|
<a class="flex gap-1 items-center" href={resource?.content?.url}>
|
||||||
link
|
source link
|
||||||
<span class="inline-block w-3 h-3 i-tabler-external-link" />
|
<span class="inline-block w-3 h-3 i-tabler-external-link" />
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
import Layout from "@layouts/Layout.astro";
|
import Layout from "@layouts/Layout.astro";
|
||||||
import HeroCard from "@components/HeroCard.astro";
|
import HeroCard from "@components/HeroCard.astro";
|
||||||
import * as memorium from "@helpers/memorium";
|
import * as memorium from "@helpers/memorium";
|
||||||
import { resources as resourceTypes } from "../resources.ts";
|
import { resources as resourceTypes } from "@content/resources.ts";
|
||||||
import { useTranslations } from "@i18n/utils";
|
import { useTranslations } from "@i18n/utils";
|
||||||
|
import ClientSearch from "@components/ClientSearch.svelte";
|
||||||
|
|
||||||
const { resourceType } = Astro.params;
|
const { resourceType } = Astro.params;
|
||||||
|
|
||||||
@@ -32,15 +33,35 @@ export async function getStaticPaths() {
|
|||||||
function isValidResource(res: any) {
|
function isValidResource(res: any) {
|
||||||
return !!res?.content?._type;
|
return !!res?.content?._type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildSearchTerm(res: any) {
|
||||||
|
if (!res) return "";
|
||||||
|
return Object.keys(res)
|
||||||
|
.map((key) => {
|
||||||
|
if (key.startsWith("_")) return;
|
||||||
|
const value = res[key];
|
||||||
|
if (Array.isArray(value)) return value.join(" ");
|
||||||
|
if (typeof value === "object") return buildSearchTerm(value);
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
.filter((s) => !!s?.length)
|
||||||
|
.join(" ")
|
||||||
|
.toLowerCase();
|
||||||
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Max Richter">
|
<Layout title="Max Richter">
|
||||||
<h1 class="text-4xl mb-4">{t(resourceType)}</h1>
|
<h1 class="text-4xl mb-4">{t(resourceType)}</h1>
|
||||||
<p>{t(`${resourceType as "articles"}.description`)}</p>
|
<p>{t(`${resourceType as "articles"}.description`)}</p>
|
||||||
|
|
||||||
|
<ClientSearch resourceType={resourceType} client:load />
|
||||||
|
|
||||||
|
<div id="resource-list-static" class="flex flex-col gap-6">
|
||||||
{
|
{
|
||||||
resources.content
|
resources?.content
|
||||||
.filter((res: any) => isValidResource(res))
|
.filter((res: any) => isValidResource(res))
|
||||||
.map((resource: any) => (
|
.map((resource: any) => (
|
||||||
|
<div data-search-term={buildSearchTerm(resource?.content)}>
|
||||||
<HeroCard
|
<HeroCard
|
||||||
post={{
|
post={{
|
||||||
collection: "resources/" + resourceType,
|
collection: "resources/" + resourceType,
|
||||||
@@ -59,6 +80,8 @@ function isValidResource(res: any) {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -1,18 +1,53 @@
|
|||||||
---
|
---
|
||||||
import Layout from "@layouts/Layout.astro";
|
import Layout from "@layouts/Layout.astro";
|
||||||
import HeroCard from "@components/HeroCard.astro";
|
import HeroCard from "@components/HeroCard.astro";
|
||||||
import { resources } from "./resources.ts";
|
import { resources } from "@content/resources.ts";
|
||||||
import { useTranslations } from "@i18n/utils";
|
import { useTranslations } from "@i18n/utils";
|
||||||
|
import * as memorium from "@helpers/memorium";
|
||||||
|
|
||||||
const t = useTranslations(Astro.url);
|
const t = useTranslations(Astro.url);
|
||||||
|
|
||||||
|
async function getCoverImage(resourceName: string) {
|
||||||
|
const resources = await memorium.listResource(resourceName);
|
||||||
|
if (!resources?.content) return "";
|
||||||
|
let amount = 0;
|
||||||
|
while (true) {
|
||||||
|
amount++;
|
||||||
|
const randomResource =
|
||||||
|
resources?.content[Math.floor(Math.random() * resources?.content.length)];
|
||||||
|
const cover =
|
||||||
|
randomResource?.content?.cover || randomResource?.content?.image;
|
||||||
|
if (cover) {
|
||||||
|
if (cover.startsWith("https://") || cover.startsWith("http://")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return `https://marka.max-richter.dev/${cover}`;
|
||||||
|
}
|
||||||
|
if (amount > 50) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Max Richter">
|
<Layout title="Max Richter">
|
||||||
{resources.map((resource) =>
|
{
|
||||||
<HeroCard post={{
|
await Promise.all(
|
||||||
|
resources.map(async (resource) => {
|
||||||
|
const cover = await getCoverImage(resource.id);
|
||||||
|
return (
|
||||||
|
<HeroCard
|
||||||
|
post={{
|
||||||
...resource,
|
...resource,
|
||||||
body: t(`${resource.id}.description`),
|
body: t(`${resource.id}.description`),
|
||||||
data: {
|
data: {
|
||||||
cover:{src:resource.cover}, title: t(resource.id)}}} />
|
cover: { src: cover },
|
||||||
)}
|
title: t(resource.id),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
Reference in New Issue
Block a user