diff --git a/svelte-app/cypress/e2e/routes/blog.cy.ts b/svelte-app/cypress/e2e/routes/blog.cy.ts index 6d5b89837..fa8adf570 100644 --- a/svelte-app/cypress/e2e/routes/blog.cy.ts +++ b/svelte-app/cypress/e2e/routes/blog.cy.ts @@ -19,7 +19,7 @@ describe('E2E | Blog', () => { cy.visit('/blog'); cy.get('div.main', { timeout: 4000 }).should('exist'); - cy.get('div.main').should('contain', 'kio.dev | Blog'); + cy.get('div.main').should('contain', 'Recent posts'); }); // TODO: This should be a separate test diff --git a/svelte-app/cypress/e2e/routes/index.cy.ts b/svelte-app/cypress/e2e/routes/index.cy.ts index b3cf28abb..a1d2ebd57 100644 --- a/svelte-app/cypress/e2e/routes/index.cy.ts +++ b/svelte-app/cypress/e2e/routes/index.cy.ts @@ -3,12 +3,12 @@ describe('E2E | Index', () => { cy.intercept('GET', '/api/get/config'); cy.visit('/'); - cy.get('div.main', { timeout: 4000 }).should('contain', 'kio.dev | Index'); + cy.get('div.main', { timeout: 4000 }).should('contain', 'About me'); cy.get('[data-test-id="error-text"]').should('not.exist'); cy.reload(); - cy.get('div.main', { timeout: 4000 }).should('contain', 'kio.dev | Index'); + cy.get('div.main', { timeout: 4000 }).should('contain', 'About me'); cy.get('[data-test-id="error-text"]').should('not.exist'); }); }); diff --git a/svelte-app/src/app.scss b/svelte-app/src/app.scss index ee462f91c..1fb935fa3 100644 --- a/svelte-app/src/app.scss +++ b/svelte-app/src/app.scss @@ -33,7 +33,7 @@ body { @media (min-width: 0px) { background-color: $dark; } - @media (min-width: 1024px) { + @media (min-width: 768px) { background-color: $black; } @@ -43,7 +43,7 @@ body { @media (min-width: 0px) { background-color: $white; } - @media (min-width: 1024px) { + @media (min-width: 768px) { background-color: $light; } diff --git a/svelte-app/src/components/about/timeline.svelte b/svelte-app/src/components/about/timeline.svelte index 8656aeec7..b8dc857c3 100644 --- a/svelte-app/src/components/about/timeline.svelte +++ b/svelte-app/src/components/about/timeline.svelte @@ -6,7 +6,7 @@ import { currentLang, t } from '$i18n'; import Settings from '$stores/settings'; - import EmptyContent from '$components/empty-content.svelte'; + import BulletPoint from '$components/bullet-point.svelte'; import Hoverable from '$components/hoverable.svelte'; import Icon from '$components/icon.svelte'; import PortableText from '$components/portable-text/portable-text.svelte'; @@ -17,7 +17,7 @@ let selected: number | null = null; - export let data: AuthorTimelineItem[] | undefined; + export let data: AuthorTimelineItem[]; const dateDisplay = (start: string, end: string | undefined) => { try { @@ -26,33 +26,16 @@ if (!endDate) { return `${new Intl.DateTimeFormat($currentLang, { - month: 'long', + month: 'short', year: 'numeric' - }).format(startDate)} ${$t('to')} ${$t('now')}`; - } - - if (startDate.getFullYear() === endDate.getFullYear()) { - if (startDate.getMonth() === endDate.getMonth()) { - return `${new Intl.DateTimeFormat($currentLang, { - month: 'long', - day: 'numeric' - }).format(startDate)} ${$t('to')} ${new Intl.DateTimeFormat($currentLang, { - day: 'numeric', - year: 'numeric' - }).format(endDate)}`; - } - return `${new Intl.DateTimeFormat($currentLang, { - month: 'long' - }).format(startDate)} ${$t('to')} ${new Intl.DateTimeFormat($currentLang, { - month: 'long' - }).format(endDate)} ${endDate.getFullYear()}`; + }).format(startDate)} - ${$t('present')}`; } return `${new Intl.DateTimeFormat($currentLang, { - month: 'long', + month: 'short', year: 'numeric' - }).format(startDate)} ${$t('to')} ${new Intl.DateTimeFormat($currentLang, { - month: 'long', + }).format(startDate)} - ${new Intl.DateTimeFormat($currentLang, { + month: 'short', year: 'numeric' }).format(endDate)}`; } catch (_) { @@ -63,104 +46,79 @@ const { reduce_motion } = Settings; -
- {#if data} -
-
- {#each data as item, i} -
- -
-

+
+ {#each data as item, i} + + +
{ + if (item.body) { + selected = selected === i ? null : i; + } + }} + on:keydown={(e) => { + if (item.body && (e.code === 'Enter' || e.code === 'Space')) { + selected = selected === i ? null : i; + } + }} + aria-expanded={selected === i} + tabindex={0} + role="button" + > +
+ +

{dateDisplay(item.range.start, item.range.end)} -

- - +
+

-
{ - if (item.body) { - selected = selected === i ? null : i; - } - }} - on:keydown={(e) => { - if (item.body && (e.code === 'Enter' || e.code === 'Space')) { - selected = selected === i ? null : i; - } - }} - aria-expanded={selected === i} - tabindex={0} - role="button" - > -
- {item.title} -
- {#if item.subtitle} -
- {item.subtitle} -
- {/if} - {#if selected === i && item.body} -
- {#if item.skills} - - {/if} -
- -
-
- {/if} - {#if item.body} - - {/if} -
- - + {item.title} +

+ {#if item.subtitle} + +

{item.subtitle}

+ {/if} +
+ {#if selected === i && item.body} +
+ {#if item.skills} + + {/if} +
+ +
+
+ {/if}
- {/each} -
- {:else} - - {/if} + + + {/each}
diff --git a/svelte-app/src/components/bullet-point.svelte b/svelte-app/src/components/bullet-point.svelte index 5979c0895..e2f1c4d7e 100644 --- a/svelte-app/src/components/bullet-point.svelte +++ b/svelte-app/src/components/bullet-point.svelte @@ -1,3 +1,4 @@
diff --git a/svelte-app/src/components/context-menu/divider.svelte b/svelte-app/src/components/context-menu/divider.svelte index 849419ef6..fa3dc3af3 100644 --- a/svelte-app/src/components/context-menu/divider.svelte +++ b/svelte-app/src/components/context-menu/divider.svelte @@ -3,5 +3,5 @@ diff --git a/svelte-app/src/components/context-menu/menu.svelte b/svelte-app/src/components/context-menu/menu.svelte index 4e68b293e..a4a91a9c4 100644 --- a/svelte-app/src/components/context-menu/menu.svelte +++ b/svelte-app/src/components/context-menu/menu.svelte @@ -64,12 +64,12 @@ diff --git a/svelte-app/src/components/controls/language-controls.svelte b/svelte-app/src/components/controls/language-toggle.svelte similarity index 74% rename from svelte-app/src/components/controls/language-controls.svelte rename to svelte-app/src/components/controls/language-toggle.svelte index df02c4f26..ac2c8958c 100644 --- a/svelte-app/src/components/controls/language-controls.svelte +++ b/svelte-app/src/components/controls/language-toggle.svelte @@ -1,18 +1,19 @@ - - diff --git a/svelte-app/src/components/layouts/page-transition.svelte b/svelte-app/src/components/layouts/page-transition.svelte index 790ec3df8..a558078d1 100644 --- a/svelte-app/src/components/layouts/page-transition.svelte +++ b/svelte-app/src/components/layouts/page-transition.svelte @@ -1,5 +1,5 @@ - - - - diff --git a/svelte-app/src/components/lists/list-item.svelte b/svelte-app/src/components/lists/list-item.svelte new file mode 100644 index 000000000..7b2889723 --- /dev/null +++ b/svelte-app/src/components/lists/list-item.svelte @@ -0,0 +1,78 @@ + + + + {#if small} + +

+ {formatDate(document.date || document._createdAt, 'med', $currentLang)} +

+

+ {document.title} +

+
+ {:else} + +
+

+ {formatDate(document.date || document._createdAt, 'dayMonth', $currentLang)} +

+
+

+ {document.title} +

+ {#if type === 'project' && document.desc} + +

{document.desc}

+ {/if} +
+
+
+ {/if} +
diff --git a/svelte-app/src/components/lists/project-item.svelte b/svelte-app/src/components/lists/project-item.svelte deleted file mode 100644 index eaa288e4b..000000000 --- a/svelte-app/src/components/lists/project-item.svelte +++ /dev/null @@ -1,91 +0,0 @@ - - - - - diff --git a/svelte-app/src/components/nav.svelte b/svelte-app/src/components/nav.svelte index 433231469..f22b2b8aa 100644 --- a/svelte-app/src/components/nav.svelte +++ b/svelte-app/src/components/nav.svelte @@ -1,108 +1,55 @@ - +
+ + +
+ +
-
{/if} diff --git a/svelte-app/src/components/nav/nav-link.svelte b/svelte-app/src/components/nav/nav-link.svelte index 08c68841a..54577b29a 100644 --- a/svelte-app/src/components/nav/nav-link.svelte +++ b/svelte-app/src/components/nav/nav-link.svelte @@ -3,45 +3,37 @@ import { goto } from '$app/navigation'; import { navigating, page } from '$app/stores'; - import { currentLang, isLocalized, t } from '$i18n'; - import { navLinks, navOpen } from '$stores/navigation'; + import { isLocalized, linkTo, t } from '$i18n'; + import { navOpen } from '$stores/navigation'; import Hoverable from '$components/hoverable.svelte'; export let link: { name: string; url: string; - active: boolean; - hovered: boolean; }, + links: { + name: string; + url: string; + }[], index: number, mobile = false, navigatingIsActive = false; let isHovered = false, - isActive = false, - linkEl: HTMLAnchorElement; + isActive = false; - const updateActive = () => { - $navLinks[index].active !== isActive && - navLinks.update((links) => ((links[index].active = isActive), links)); - }, - updateHovered = () => { - $navLinks[index].hovered !== isHovered && - navLinks.update((links) => ((links[index].hovered = isHovered), links)); - }, - handleAction = (e: Event) => { - e.preventDefault(); - if ($page?.url.pathname.slice($isLocalized ? 3 : 0) === link.url) { - return; - } - if (mobile) { - navOpen.set(false); - } - goto(link.url).catch(() => undefined); - }; + const handleAction = (e: Event) => { + e.preventDefault(); + if ($page?.url.pathname.slice($isLocalized ? 3 : 0) === link.url) { + return; + } + if (mobile) { + navOpen.set(false); + } + goto(link.url).catch(() => undefined); + }; - $: ($navLinks[index].element = linkEl), [$currentLang]; $: splitPath = $page?.url.pathname.split('/') || []; $: truePath = link.url.slice($isLocalized ? 4 : 1); $: (isActive = (() => { @@ -54,20 +46,18 @@ return urlIncludesLink || (splitPath?.length > 1 && splitPath.indexOf(truePath) > 0); })()), navOpen; - $: updateActive(), isActive; - $: updateHovered(), isHovered; {$t(link.name)} - {#if mobile && (isActive || link.hovered)} + {#if mobile && (isActive || isHovered)} {/if} @@ -93,16 +82,13 @@ diff --git a/svelte-app/src/components/tags.svelte b/svelte-app/src/components/tags.svelte index 0d1e62cc8..2611ef4de 100644 --- a/svelte-app/src/components/tags.svelte +++ b/svelte-app/src/components/tags.svelte @@ -21,7 +21,8 @@
{ const date = new Date(dateStr); @@ -37,6 +38,11 @@ export const formatDate = ( day: 'numeric', year: '2-digit' }).format(date); + case 'dayMonth': + return new Intl.DateTimeFormat(lang, { + month: 'short', + day: 'numeric' + }).format(date); case 'rel': { const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' @@ -71,3 +77,33 @@ export const formatDate = ( */ export const getReadingTime = (words: number): number => Math.ceil(words ?? 0 / (100 / 60)); + +export const sortDocumentsByYear = ( + documents: T[] +) => { + const _years = + documents.reduce((acc, doc) => { + const year = new Date(doc.date || doc._createdAt).getFullYear(); + + if (!acc[year]) { + acc[year] = [doc]; + } else { + acc[year].push(doc); + } + + return acc; + }, {} as Record>) || {}; + + return Object.keys(_years) + .sort((a, b) => parseInt(b, 10) - parseInt(a, 10)) + .map((year) => { + return { + year: parseInt(year, 10), + items: _years[year].sort((a, b) => { + const aDate = new Date(a.date || a._createdAt); + const bDate = new Date(b.date || b._createdAt); + return bDate.getTime() - aDate.getTime(); + }) + }; + }); +}; diff --git a/svelte-app/src/lib/helpers/i18n.ts b/svelte-app/src/lib/helpers/i18n.ts index 76818f981..b3312e614 100644 --- a/svelte-app/src/lib/helpers/i18n.ts +++ b/svelte-app/src/lib/helpers/i18n.ts @@ -66,7 +66,13 @@ const _translate = (key: string, params?: Record): string => { }; const string = getKey(lang || DEFAULT_APP_LANG, key as keyof typeof EN); - return string ? replaceParams(string) : notFound(key, lang || DEFAULT_APP_LANG); + + if (string) { + return replaceParams(string); + } else { + notFound(key, lang || DEFAULT_APP_LANG); + return replaceParams(key); + } }; // eslint-disable-next-line func-call-spacing diff --git a/svelte-app/src/lib/helpers/navigation.ts b/svelte-app/src/lib/helpers/navigation.ts index 43a0fc255..ed81fbc4f 100644 --- a/svelte-app/src/lib/helpers/navigation.ts +++ b/svelte-app/src/lib/helpers/navigation.ts @@ -1,48 +1,7 @@ import { get } from 'svelte/store'; -import { isLocalized, t } from '$i18n'; -import { ROUTE_ORDER, TOP_LEVEL_ROUTES } from '$lib/consts'; -import { navOptions, pageHeading } from '$stores/navigation'; - -export const setupNavigation = (route: string): void => { - get(isLocalized) === true && - (route = route.slice(3).startsWith('/') ? route.slice(3) : `/${route.slice(3)}`); - - if (!route || route === '') { - navOptions.set({ down: '', up: '' }); - pageHeading.set(''); - return; - } - - const index = TOP_LEVEL_ROUTES.findIndex((r) => r.path === route); - - switch (index) { - case -1: - navOptions.set({ down: '', up: '' }); - pageHeading.set(''); - return; - case 0: - navOptions.set({ - down: TOP_LEVEL_ROUTES[index + 1].path, - up: '' - }); - pageHeading.set(''); - return; - case TOP_LEVEL_ROUTES.length - 1: - navOptions.set({ - down: '', - up: TOP_LEVEL_ROUTES[index - 1].path - }); - break; - default: - navOptions.set({ - down: TOP_LEVEL_ROUTES[index + 1].path, - up: TOP_LEVEL_ROUTES[index - 1].path - }); - break; - } - pageHeading.set(`kio.dev | ${get(t)(TOP_LEVEL_ROUTES[index].name)}`); -}; +import { isLocalized } from '$i18n'; +import { ROUTE_ORDER } from '$lib/consts'; let prevPath: string; diff --git a/svelte-app/src/routes/+error.svelte b/svelte-app/src/routes/+error.svelte index a6d1c7172..5331cbcc7 100644 --- a/svelte-app/src/routes/+error.svelte +++ b/svelte-app/src/routes/+error.svelte @@ -1,19 +1,11 @@ @@ -50,16 +29,22 @@ -{#if pinned} - - -{/if} - - +

{$t('Recent posts')}

{#if posts?.length} -
- {#each posts as post} - +
+ {#each sortedPosts as yearObj} +
+

{yearObj.year}

+ {#if yearObj.items.length} +
+ {#each yearObj.items as item} + + {/each} +
+ {:else} +

{$t('No content')}

+ {/if} +
{/each}
{:else} diff --git a/svelte-app/src/routes/[[lang=lang]]/blog/+page.ts b/svelte-app/src/routes/[[lang=lang]]/blog/+page.ts index 84d9916f2..3454aa33a 100644 --- a/svelte-app/src/routes/[[lang=lang]]/blog/+page.ts +++ b/svelte-app/src/routes/[[lang=lang]]/blog/+page.ts @@ -1,38 +1,18 @@ import { RECENT_POSTS_COUNT } from '$lib/consts'; import { ENV } from '$lib/env'; -import { find, findOne } from '$lib/store'; +import { find } from '$lib/store'; import type { PageLoad } from './$types'; -import type { PostDocument } from '$types'; export const ssr = !(ENV === 'testing'); export const load: PageLoad = async ({ parent, fetch, params }) => { - const parentData = await parent(), - currentConfig = parentData.config; + const parentData = await parent(); - const promiseArray = []; + const posts = await find(fetch, 'post', { + limit: RECENT_POSTS_COUNT, + lang: params.lang ?? 'en' + }); - promiseArray.push( - find(fetch, 'post', { - limit: RECENT_POSTS_COUNT, - lang: params.lang ?? 'en' - }) - ); - - if (currentConfig?.pinnedPost?._ref) { - promiseArray.push( - findOne(fetch, 'post', { - id: currentConfig.pinnedPost._ref, - lang: params.lang ?? 'en' - }) - ); - } - - const [posts, pinned] = (await Promise.all(promiseArray)) as [ - PostDocument[] | undefined, - PostDocument | undefined - ]; - - return { posts, pinned }; + return { posts }; }; diff --git a/svelte-app/src/routes/[[lang=lang]]/blog/[plus=plus]/[slug]/+page.svelte b/svelte-app/src/routes/[[lang=lang]]/blog/[plus=plus]/[slug]/+page.svelte index 2e29d04a4..66b7b65a5 100644 --- a/svelte-app/src/routes/[[lang=lang]]/blog/[plus=plus]/[slug]/+page.svelte +++ b/svelte-app/src/routes/[[lang=lang]]/blog/[plus=plus]/[slug]/+page.svelte @@ -1,19 +1,13 @@ @@ -22,15 +16,17 @@ - -{#if posts?.length} -
- {#each posts as post} - +

+ {$t("Recent '{tag}' posts", { tag: pageTitle })} +

+{#if data.posts?.length} +
+ {#each data.posts as post} + {/each}
{:else} -
+
{/if} diff --git a/svelte-app/src/routes/[[lang=lang]]/etc/+page.svelte b/svelte-app/src/routes/[[lang=lang]]/etc/+page.svelte index dfc69c10e..a13c5e610 100644 --- a/svelte-app/src/routes/[[lang=lang]]/etc/+page.svelte +++ b/svelte-app/src/routes/[[lang=lang]]/etc/+page.svelte @@ -1,10 +1,7 @@ @@ -41,31 +32,35 @@ - -
- -
- - -{#if pinned} -
- +

{$t("Where I've worked")}

+{#if about?.timeline?.length} + +{:else} +
+
- - {/if} + +

{$t('Projects & Talks')}

{#if projects?.length} -
- {#each projects as project} - {#if project._id !== pinned?._id} - - {/if} +
+ {#each sortedProjects as yearObj} +
+

{yearObj.year}

+ {#if yearObj.items.length} +
+ {#each yearObj.items as item} + + {/each} +
+ {:else} +

{$t('No content')}

+ {/if} +
{/each}
{:else} -
+
{/if} diff --git a/svelte-app/src/routes/[[lang=lang]]/work/+page.ts b/svelte-app/src/routes/[[lang=lang]]/work/+page.ts index 44af84bba..6d17d0682 100644 --- a/svelte-app/src/routes/[[lang=lang]]/work/+page.ts +++ b/svelte-app/src/routes/[[lang=lang]]/work/+page.ts @@ -6,8 +6,7 @@ import type { AuthorDocument, ProjectDocument } from '$types'; export const load: PageLoad = async ({ parent, fetch, params }) => { const parentData = await parent(), - aboutData = parentData.author, - currentConfig = parentData.config; + aboutData = parentData.author; const promiseArray = []; @@ -29,20 +28,10 @@ export const load: PageLoad = async ({ parent, fetch, params }) => { }) ); - if (currentConfig?.pinnedProject?._ref) { - promiseArray.push( - findOne(fetch, 'project', { - id: currentConfig.pinnedProject._ref, - lang: params.lang ?? 'en' - }) - ); - } - - const [about, projects, pinned] = (await Promise.all(promiseArray)) as [ + const [about, projects] = (await Promise.all(promiseArray)) as [ AuthorDocument | undefined, - ProjectDocument[] | undefined, - ProjectDocument | undefined + ProjectDocument[] | undefined ]; - return { about, pinned, projects }; + return { about, projects }; }; diff --git a/svelte-app/src/routes/[[lang=lang]]/work/[plus=plus]/[slug]/+page.svelte b/svelte-app/src/routes/[[lang=lang]]/work/[plus=plus]/[slug]/+page.svelte index f5ca0171d..f42011cd1 100644 --- a/svelte-app/src/routes/[[lang=lang]]/work/[plus=plus]/[slug]/+page.svelte +++ b/svelte-app/src/routes/[[lang=lang]]/work/[plus=plus]/[slug]/+page.svelte @@ -1,21 +1,11 @@ @@ -24,13 +14,13 @@ - -{#if projects?.length} -
- {#each projects as project} - +

+ {$t("Recent '{tag}' projects", { tag: pageTitle })} +

+{#if $page.data.projects?.length} +
+ {#each $page.data.projects as project} + {/each}
{:else} diff --git a/svelte-app/src/stores/navigation.ts b/svelte-app/src/stores/navigation.ts index cf50e545b..85db03056 100644 --- a/svelte-app/src/stores/navigation.ts +++ b/svelte-app/src/stores/navigation.ts @@ -2,18 +2,4 @@ import { writable } from 'svelte/store'; const navOpen = writable(false); -const navOptions = writable({ down: '', up: '' }); - -const pageHeading = writable(''); - -const navLinks = writable< - { - name: string; - url: string; - active: boolean; - hovered: boolean; - element?: HTMLAnchorElement; - }[] ->([]); - -export { navLinks, navOpen, navOptions, pageHeading }; +export { navOpen }; diff --git a/svelte-app/src/styles/components/_code-block.scss b/svelte-app/src/styles/components/_code-block.scss index 30ce061ef..229c202a0 100644 --- a/svelte-app/src/styles/components/_code-block.scss +++ b/svelte-app/src/styles/components/_code-block.scss @@ -27,7 +27,7 @@ } .codeBlock--copyButton { - @apply absolute top-0 right-0 z-10 cursor-pointer rounded-sm p-4 text-dark/60 opacity-0 transition-opacity duration-150; + @apply absolute top-0 right-0 z-[1] cursor-pointer rounded-sm p-4 text-dark/60 opacity-0 transition-opacity duration-150; &:hover { @apply text-dark; diff --git a/svelte-app/types/app/documents/post.d.ts b/svelte-app/types/app/documents/post.d.ts index d51009be1..b5fc6501f 100644 --- a/svelte-app/types/app/documents/post.d.ts +++ b/svelte-app/types/app/documents/post.d.ts @@ -2,6 +2,7 @@ import type { Document, DocumentHeadings, DocumentTags } from '$types/documents' import type { SanityAsset, SanityImageObject } from '$types/sanity'; export interface PostDocument extends Document { + _type: 'post'; author?: Pick & { name: string; image: SanityImageObject; diff --git a/svelte-app/types/app/documents/project.d.ts b/svelte-app/types/app/documents/project.d.ts index 993d9e5ff..ea091c4e4 100644 --- a/svelte-app/types/app/documents/project.d.ts +++ b/svelte-app/types/app/documents/project.d.ts @@ -2,6 +2,7 @@ import type { Document, DocumentHeadings, DocumentTags } from '$types/documents' import type { SanityAsset, SanityImageObject } from '$types/sanity'; export interface ProjectDocument extends Document { + _type: 'project'; author?: { _id: string; _type: string; diff --git a/svelte-app/types/app/sanity/index.d.ts b/svelte-app/types/app/sanity/index.d.ts index 664f2deca..16db07334 100644 --- a/svelte-app/types/app/sanity/index.d.ts +++ b/svelte-app/types/app/sanity/index.d.ts @@ -1,15 +1,12 @@ export type { InputValue } from '@portabletext/svelte/ptTypes'; -export type { - PortableTextBlock, - ArbitraryTypedObject -} from '@portabletext/types'; +export type { ArbitraryTypedObject, PortableTextBlock } from '@portabletext/types'; export interface SanityAsset { _id: string; - _type?: string; - _createdAt?: string; - _rev?: string; - _updatedAt?: string; + _type: string; + _createdAt: string; + _rev: string; + _updatedAt: string; url?: string; path?: string; assetId?: string;