Blog layouts and test posts
This commit is contained in:
parent
889825672d
commit
434c214908
|
@ -1,10 +1,9 @@
|
|||
import type { BlogPost } from '$lib/types/blog';
|
||||
import type { MarkdownMetadata } from '$lib/contents/types';
|
||||
import type { MarkdownMetadata } from '$content/types';
|
||||
import type { MdsvexImport } from './types';
|
||||
import { parseReadContent } from '../../content/utils';
|
||||
import { parseReadContent } from '$content/utils';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
|
||||
export function listBlogPosts() {
|
||||
const posts = import.meta.glob<BlogPost>('./blog/*.md', {
|
||||
eager: true,
|
||||
|
|
|
@ -7,9 +7,27 @@ tags:
|
|||
- post
|
||||
published: true
|
||||
image: Feature.jpg
|
||||
|
||||
---
|
||||
|
||||
## Svelte
|
||||
|
||||
Media inside the **Svelte** folder is served from the `static` folder.
|
||||
|
||||
```python
|
||||
|
||||
input_text = ''' "yahooapis.com",
|
||||
"hotmail.com",
|
||||
"gfx.ms",
|
||||
"afx.ms",
|
||||
"live.com",
|
||||
'''
|
||||
# and so on...
|
||||
|
||||
lines = input_text.split('\n')
|
||||
|
||||
formatted_lines = ['* ' + line.strip()[1:-2] + ' * block' for line in lines if line]
|
||||
|
||||
output_text = '\n'.join(formatted_lines)
|
||||
print(output_text)
|
||||
|
||||
```
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
import type { create_ssr_component } from 'svelte/internal';
|
||||
type DateStyle = Intl.DateTimeFormatOptions['dateStyle'];
|
||||
|
||||
/**
|
||||
* Sorts an array of objects by their `date` property in descending order.
|
||||
* @param a - The first object to compare.
|
||||
* @param b - The second object to compare.
|
||||
* @returns A number that represents the difference between the parsed dates of the two objects.
|
||||
*/
|
||||
export function dateSort<T extends { date?: string }>(a: T, b: T): number {
|
||||
return Date.parse(b.date) - Date.parse(a.date);
|
||||
}
|
||||
|
||||
export function renderMdsvexComponent(component: any): string {
|
||||
if (typeof component['render'] != 'function') {
|
||||
throw new Error(
|
||||
"Unable to render something that isn't a mdsvex component",
|
||||
);
|
||||
}
|
||||
|
||||
return (component as ReturnType<typeof create_ssr_component>).render().html;
|
||||
}
|
||||
|
||||
export function mdPathToSlug(path: string) {
|
||||
return path.split('/').at(-1).slice(0, -3);
|
||||
}
|
||||
|
||||
export function parseReadContent<T extends { date?: string }>(
|
||||
data: Record<string, T>,
|
||||
): T[] {
|
||||
return Object.entries(data)
|
||||
.map(([file, data]) => ({
|
||||
slug: mdPathToSlug(file),
|
||||
...data,
|
||||
}))
|
||||
.sort(dateSort);
|
||||
}
|
||||
|
||||
// Old utils
|
||||
// /**
|
||||
// * Formats a date string using the specified date style and locale.
|
||||
// * @param date - The date string to format.
|
||||
// * @param dateStyle - The style to use when formatting the date. Defaults to 'medium'.
|
||||
// * @param locales - The locale to use when formatting the date. Defaults to 'en'.
|
||||
// * @returns The formatted date string.
|
||||
// */
|
||||
// // export function formatDate(date: string, dateStyle: DateStyle = 'medium', locales = 'en') {
|
||||
// // const formatter = new Intl.DateTimeFormat(locales, { dateStyle });
|
||||
// // return formatter.format(new Date(date));
|
||||
// // }
|
||||
|
||||
// /**
|
||||
// * Renders an mdsvex component and returns the resulting HTML string.
|
||||
// * @param component - The mdsvex component to render.
|
||||
// * @returns The HTML string that was generated by rendering the component.
|
||||
// * @throws An error if the `render` property of the component is not a function.
|
||||
// */
|
||||
// export function renderMdsvexComponent(component: ReturnType<typeof create_ssr_component>): string {
|
||||
// if (typeof component['render'] !== 'function') {
|
||||
// throw new Error("Unable to render something that isn't a mdsvex component");
|
||||
// }
|
||||
|
||||
// return component.render().html;
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Converts an md file path to a slug by removing the last segment of the path and the `.md` extension.
|
||||
// * @param path - The path of the md file.
|
||||
// * @returns The slug of the md file.
|
||||
// */
|
||||
// export function mdPathToSlug(path: string): string {
|
||||
// return (path.split('/').at(-1) || '')?.slice(0, -3) || '';
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Parses an object of data that has string keys and values of type `T` that have an optional `date` property.
|
||||
// * @param data - The object of data to parse.
|
||||
// * @param dateSort - A function that sorts an array of objects by their `date` property in descending order.
|
||||
// * @param mdPathToSlug - A function that converts an md file path to a slug.
|
||||
// * @returns An array of objects that have a `slug` property and the properties of the original data objects.
|
||||
// */
|
||||
// export function parseReadContent<T extends { date?: string }>(data: Record<string, T>): T[] {
|
||||
// return Object.entries(data)
|
||||
// .map(
|
||||
// ([file, data]) =>
|
||||
// ({
|
||||
// slug: mdPathToSlug(file),
|
||||
// ...data
|
||||
// } as { slug: string } & T & { date: string })
|
||||
// )
|
||||
// .sort(dateSort);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
import { readable } from 'svelte/store';
|
||||
|
||||
export const isEurope = () => {
|
||||
const offset = new Date().getTimezoneOffset();
|
||||
return offset <= 0 && offset >= -180;
|
||||
};
|
||||
|
||||
export const stringToBeautifiedFragment = (str: string = '') =>
|
||||
(str || '')
|
||||
.toLocaleLowerCase()
|
||||
.replace(/\s/g, '-')
|
||||
.replace(/\?/g, '')
|
||||
.replace(/,/g, '');
|
||||
|
||||
export const showHideOverflowY = (bool: boolean) => {
|
||||
const html = document.querySelector('html');
|
||||
if (bool) {
|
||||
html.classList.add('overflow-y-hidden');
|
||||
} else {
|
||||
html.classList.remove('overflow-y-hidden');
|
||||
}
|
||||
};
|
||||
|
||||
export const formatDate = (date) => {
|
||||
try {
|
||||
const d = new Date(date);
|
||||
return `${d.toLocaleString('default', {
|
||||
month: 'long',
|
||||
})} ${d.getDate()}, ${d.getFullYear()}`;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export const scrollToElement = async (
|
||||
element: HTMLElement,
|
||||
selector: string,
|
||||
) => {
|
||||
const firstElement: HTMLElement = element.querySelector(selector);
|
||||
if (!firstElement) {
|
||||
return;
|
||||
}
|
||||
firstElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
});
|
||||
};
|
||||
|
||||
export const removeTrailingSlash = (site: string) => {
|
||||
return site.replace(/\/$/, '');
|
||||
};
|
||||
|
||||
|
||||
export const useMediaQuery = (mediaQueryString: string) => {
|
||||
const matches = readable<boolean>(null, (set) => {
|
||||
if (typeof globalThis['window'] === 'undefined') return;
|
||||
|
||||
const match = window.matchMedia(mediaQueryString);
|
||||
set(match.matches);
|
||||
const element = (event: MediaQueryListEvent) => set(event.matches);
|
||||
match.addEventListener('change', element);
|
||||
return () => {
|
||||
match.removeEventListener('change', element);
|
||||
};
|
||||
});
|
||||
return matches;
|
||||
};
|
||||
|
||||
export const scrollIntoView = (selector: string) => {
|
||||
const scrollToElement = document.querySelector(selector);
|
||||
|
||||
if (!scrollToElement) return;
|
||||
|
||||
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
|
||||
scrollToElement.scrollIntoView({
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
behavior: mediaQuery.matches ? 'auto' : 'smooth',
|
||||
});
|
||||
};
|
||||
export const getVariantFromStatus = (status: string) => {
|
||||
if (status === 'soon' || status === 'Early Access') {
|
||||
return 'pink';
|
||||
} else {
|
||||
return 'orange';
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* Nord Theme Originally by Arctic Ice Studio
|
||||
* https://nordtheme.com
|
||||
*
|
||||
* Ported for PrismJS by Zane Hitchcoxc (@zwhitchcox) and Gabriel Ramos (@gabrieluizramos)
|
||||
*/
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #f8f8f2;
|
||||
background: none;
|
||||
font-family: "Fira Code", Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #2E3440;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: #636f88;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #81A1C1;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #81A1C1;
|
||||
}
|
||||
|
||||
.token.number {
|
||||
color: #B48EAD;
|
||||
}
|
||||
|
||||
.token.boolean {
|
||||
color: #81A1C1;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #A3BE8C;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.variable {
|
||||
color: #81A1C1;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #88C0D0;
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: #81A1C1;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: #EBCB8B;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<script lang="ts">
|
||||
import PostContentLayout from './PostLayout.svelte';
|
||||
|
||||
import type { BlogPost } from '$lib/types/blog';
|
||||
|
||||
export let post: BlogPost;
|
||||
</script>
|
||||
|
||||
<PostContentLayout {...post} imagesDirectoryName="blog">
|
||||
<slot />
|
||||
</PostContentLayout>
|
|
@ -0,0 +1,38 @@
|
|||
<script lang="ts">
|
||||
import type { BlogTag } from '$lib/types/blog';
|
||||
export let selected: BlogTag;
|
||||
let className = '';
|
||||
export { className as class };
|
||||
import { page } from '$app/stores';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
let options: BlogTag[] = ['Projects', 'Blog', 'Updates'];
|
||||
|
||||
const clickHandler = (value: BlogTag) => {
|
||||
if (value === selected) {
|
||||
goto(`/blog`, { keepFocus: true, noScroll: true });
|
||||
selected = '';
|
||||
return;
|
||||
}
|
||||
let query = new URLSearchParams($page.url.searchParams.toString());
|
||||
query.set('tag', value);
|
||||
goto(`?${query.toString()}`, { keepFocus: true, noScroll: true });
|
||||
selected = value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<section class="flex justify-center flex-col items-center {className}">
|
||||
<p class="text-semibold mb-2 md:mb-4">Sort by category</p>
|
||||
<ul class="flex flex-wrap justify-center gap-2">
|
||||
{#each options as option}
|
||||
<li>
|
||||
<button
|
||||
class="btn btn-md variant-filled-primary"
|
||||
on:click={() => clickHandler(option)}
|
||||
>
|
||||
{option}
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
|
@ -1,35 +1,40 @@
|
|||
<script lang="ts">
|
||||
import { formatDate } from '$src/content/utils';
|
||||
import { formatDate } from '$content/utils';
|
||||
import '$lib/assets/prism-nord.css';
|
||||
|
||||
export let baseUrl: string;
|
||||
export let imagesDirectoryName: string;
|
||||
|
||||
export let date: string = '';
|
||||
export let slug: string = '';
|
||||
export let title: string;
|
||||
export let image: string;
|
||||
export let teaserImage: string;
|
||||
export let tags: string[] = [];
|
||||
</script>
|
||||
|
||||
<article>
|
||||
<div class="flex justify-center mt-4 mb-8">
|
||||
<div class="w-full lg:w-[50rem] leading-[177.7%]">
|
||||
<header>
|
||||
<img
|
||||
src="/images/{imagesDirectoryName}/{slug}/{teaserImage || image}"
|
||||
src="/images/{imagesDirectoryName}/{slug}/{image}"
|
||||
alt={`${title}`}
|
||||
class="max-h-[540px] rounded-tl-2xl rounded-tr-[1.3rem]"
|
||||
class=" bg-black/50 w-full aspect-[21/9] max-h-[540px] rounded-tr-[1.3rem]"
|
||||
/>
|
||||
<div
|
||||
class="prose prose-img:rounded-tl-2xl prose-img:rounded-tr-[1.3rem] max-w-none text-base"
|
||||
>
|
||||
<p class="{tags && tags.length > 0 ? '!mb-2' : '!mb-2'} mt-8 text-base">
|
||||
{formatDate(date)}
|
||||
</p>
|
||||
</header>
|
||||
<div class="p-4 space-y-4">
|
||||
<h3 class="h3" data-toc-ignore>{title}</h3>
|
||||
<div class="prose max-w-none text-base">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="opacity-50" />
|
||||
<footer class="p-4 flex justify-start items-center space-x-4">
|
||||
<div class="flex-auto flex justify-between items-center">
|
||||
{#if tags && tags.length > 0}
|
||||
<div class="flex mb-2 items-center gap-2">
|
||||
{#each tags as tag}
|
||||
tags: {#each tags as tag}
|
||||
<a
|
||||
data-sveltekit-preload-data="hover"
|
||||
href="/blog?{new URLSearchParams({ tag }).toString()}"
|
||||
|
@ -39,10 +44,9 @@
|
|||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
<small>On {formatDate(date)}</small>
|
||||
</div>
|
||||
</footer>
|
||||
</article>
|
||||
|
||||
<style lang="postcss">
|
||||
|
|
|
@ -3,10 +3,8 @@
|
|||
import type { BlogPost } from '$lib/types/blog';
|
||||
|
||||
export let post: BlogPost;
|
||||
export let isMostRecent: boolean = false;
|
||||
export let type: 'blog';
|
||||
export let layout: 'row' | 'column' = 'column';
|
||||
export let published: boolean = true;
|
||||
export let published: boolean;
|
||||
export let headlineOrder: 'h3' | '' = '';
|
||||
export let badge: string = '';
|
||||
export let textWidth: string = '';
|
||||
|
@ -19,6 +17,12 @@
|
|||
$: href = generateURL(post['href'], post.slug);
|
||||
|
||||
$: target = post && post['href'] && isAnExternalLink(post['href']) ? '_blank' : undefined;
|
||||
|
||||
const displayDate = new Date(Date.parse(post.date)).toLocaleDateString(undefined, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
</script>
|
||||
|
||||
<a
|
||||
|
@ -67,11 +71,7 @@
|
|||
<small>
|
||||
{#if post.date}
|
||||
<span class="date text-p-small ml-macro">
|
||||
{new Date(Date.parse(post.date)).toLocaleDateString(undefined, {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
})}
|
||||
{displayDate}
|
||||
</span>
|
||||
{/if}
|
||||
</small>
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import type { MarkdownMetadata } from '$lib/contents/types';
|
||||
import type { MarkdownMetadata } from '../../../src/content/types';
|
||||
|
||||
export type BlogTag = 'Projects' | 'Blog' | 'Updates' | '';
|
||||
|
||||
export interface BlogPost extends MarkdownMetadata {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
tags?: BlogTag[];
|
||||
modified?: string;
|
||||
author?: string;
|
||||
date?: string;
|
||||
excerpt: string;
|
||||
image: string;
|
||||
slug?: string;
|
||||
href?: string;
|
||||
published: boolean;
|
||||
tags?: BlogTag[];
|
||||
subtitle?: string;
|
||||
teaserImage: string;
|
||||
title: string;
|
||||
isNotAnActualPost?: boolean;
|
||||
type?: string;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
import { readable } from 'svelte/store';
|
||||
|
||||
/**
|
||||
* Determines if the current timezone is between 0 and +3 hours UTC.
|
||||
* @returns {boolean} True if the timezone is between 0 and +3 hours UTC, false otherwise.
|
||||
*/
|
||||
export const isEurope = () => {
|
||||
const offset = new Date().getTimezoneOffset();
|
||||
return offset <= 0 && offset >= -180; // Returns true if the timezone is between 0 and +3 hours UTC, false otherwise
|
||||
};
|
||||
/**
|
||||
* Takes a string and returns a beautified version of it.
|
||||
* @param str The input string to be beautified.
|
||||
* @returns The beautified string.
|
||||
*/
|
||||
export const stringToBeautifiedFragment = (str = '') =>
|
||||
(str || '').toLocaleLowerCase().replace(/\s/g, '-').replace(/\?/g, '').replace(/,/g, '');
|
||||
/**
|
||||
* Toggles the 'overflow-y-hidden' class on the 'html' element of the document.
|
||||
* @param bool A boolean value indicating whether to show or hide the overflow-y scrollbar.
|
||||
*/
|
||||
export const showHideOverflowY = (bool: boolean) => {
|
||||
const html = document.querySelector('html');
|
||||
if (html) {
|
||||
if (bool) {
|
||||
html.classList.add('overflow-y-hidden');
|
||||
} else {
|
||||
html.classList.remove('overflow-y-hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats a date string into a human-readable format.
|
||||
* @param date - The date string to format.
|
||||
* @returns A string representing the formatted date, or an empty string if the input is invalid.
|
||||
*/
|
||||
export const formatDate = (date: string) => {
|
||||
try {
|
||||
const d = new Date(date);
|
||||
return `${d.toLocaleString('default', {
|
||||
month: 'long'
|
||||
})} ${d.getDate()}, ${d.getFullYear()}`;
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Scrolls to the first element that matches the given selector within the provided element.
|
||||
* @param element The element to search within.
|
||||
* @param selector The selector to match against.
|
||||
*/
|
||||
export const scrollToElement = async (element: HTMLElement, selector: string) => {
|
||||
const firstElement: HTMLElement | null = element.querySelector(selector);
|
||||
if (!firstElement) {
|
||||
return;
|
||||
}
|
||||
firstElement.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a given URL is an external link.
|
||||
* @param href - The URL to check.
|
||||
* @returns True if the URL is an external link, false otherwise.
|
||||
*/
|
||||
export const isAnExternalLink = (href: string) => href.startsWith('http');
|
||||
|
||||
/**
|
||||
* Checks if the user agent is running on a Mac or iPad.
|
||||
* @returns {boolean} Returns true if the user agent is running on a Mac or iPad, false otherwise.
|
||||
*/
|
||||
export const isMac = () =>
|
||||
navigator.userAgent.includes('Macintosh') || navigator.userAgent.includes('iPad');
|
||||
|
||||
/**
|
||||
* Removes the trailing slash from a given string.
|
||||
* @param site - The string to remove the trailing slash from.
|
||||
* @returns The string without the trailing slash.
|
||||
*/
|
||||
export const removeTrailingSlash = (site: string) => {
|
||||
return site.replace(/\/$/, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a readable store that tracks whether the media query string matches the current viewport.
|
||||
* @param mediaQueryString - The media query string to match against the viewport.
|
||||
* @returns A readable store that tracks whether the media query string matches the current viewport.
|
||||
*/
|
||||
export const useMediaQuery = (mediaQueryString: string) => {
|
||||
const matches = readable<boolean | undefined>(undefined, (set) => {
|
||||
if (typeof globalThis['window'] === 'undefined') return;
|
||||
|
||||
const match = window.matchMedia(mediaQueryString);
|
||||
set(match.matches);
|
||||
const element = (event: MediaQueryListEvent) => set(event.matches);
|
||||
match.addEventListener('change', element);
|
||||
return () => {
|
||||
match.removeEventListener('change', element);
|
||||
};
|
||||
});
|
||||
return matches;
|
||||
};
|
||||
|
||||
/**
|
||||
* Scrolls the page to the nearest element matching the given selector.
|
||||
* @param selector - The CSS selector of the element to scroll to.
|
||||
*/
|
||||
export const scrollIntoView = (selector: string) => {
|
||||
const scrollToElement = document.querySelector(selector);
|
||||
|
||||
if (!scrollToElement) return;
|
||||
|
||||
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
|
||||
scrollToElement.scrollIntoView({
|
||||
block: 'nearest',
|
||||
inline: 'start',
|
||||
behavior: mediaQuery.matches ? 'auto' : 'smooth'
|
||||
});
|
||||
};
|
|
@ -43,7 +43,7 @@
|
|||
</section> -->
|
||||
|
||||
<script lang="ts">
|
||||
import PostPreview from '$lib/components/blog/BlogLayout.svelte';
|
||||
import PostPreview from '$lib/components/blog/PostPreview.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import type { BlogTag } from '$lib/types/blog';
|
||||
import { page } from '$app/stores';
|
||||
|
|
Loading…
Reference in New Issue