Merge pull request #9 from michalvankodev/dev

Broadcasts segment
This commit is contained in:
Michal Vanko 2023-02-21 22:09:17 +01:00 committed by GitHub
commit f9b68c52be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 503 additions and 193 deletions

2
.nvmrc
View File

@ -1 +1 @@
v16.15.1 lts/*

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: How I've built my website title: How I've built my website
segments:
- blog
published: true published: true
date: 2020-02-28T16:00:55.791Z date: 2020-02-28T16:00:55.791Z
thumbnail: /images/uploads/DSC01202.jpg thumbnail: /images/uploads/DSC01202.jpg

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: Custom Redox keyboard assembly title: Custom Redox keyboard assembly
segments:
- blog
published: true published: true
date: 2020-04-10T15:16:54.820Z date: 2020-04-10T15:16:54.820Z
thumbnail: /images/uploads/img_20200301_171735.jpg thumbnail: /images/uploads/img_20200301_171735.jpg

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: Transition to Colemak keyboard layout title: Transition to Colemak keyboard layout
segments:
- blog
published: true published: true
date: 2020-05-11T05:38:18.797Z date: 2020-05-11T05:38:18.797Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: Samsung Galaxy Buds+ Review title: Samsung Galaxy Buds+ Review
segments:
- blog
published: true published: true
date: 2020-06-07T22:58:18.797Z # TODO executable line for generating date date: 2020-06-07T22:58:18.797Z # TODO executable line for generating date
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: I've made an RSS feed title: I've made an RSS feed
segments:
- blog
published: true published: true
date: 2020-06-15T15:48:18.797Z # TODO executable line for generating date date: 2020-06-15T15:48:18.797Z # TODO executable line for generating date
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: WebAssembly briefing title: WebAssembly briefing
segments:
- blog
published: true published: true
date: 2020-08-05T15:38:18.797Z # TODO executable line for generating date date: 2020-08-05T15:38:18.797Z # TODO executable line for generating date
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: How to deal with technical debt title: How to deal with technical debt
segments:
- blog
published: true published: true
date: 2020-11-08T15:57:18.797Z date: 2020-11-08T15:57:18.797Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: I've moved my site to Netlify title: I've moved my site to Netlify
segments:
- blog
published: true published: true
date: 2020-11-20T20:35:05.230Z date: 2020-11-20T20:35:05.230Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: Guide on error handling title: Guide on error handling
segments:
- blog
published: true published: true
date: 2020-12-09T14:44:11.948Z date: 2020-12-09T14:44:11.948Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: Logging recommendations title: Logging recommendations
segments:
- blog
published: true published: true
date: 2020-12-18T14:39:53.533Z date: 2020-12-18T14:39:53.533Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: Podcast episode about Agile development title: Podcast episode about Agile development
segments:
- blog
published: true published: true
date: 2021-02-16T09:09:57.102Z date: 2021-02-16T09:09:57.102Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: Introduction to JavaScript Application testing title: Introduction to JavaScript Application testing
segments:
- blog
published: true published: true
date: 2021-05-07T14:44:57.102Z # update date accordingly date: 2021-05-07T14:44:57.102Z # update date accordingly
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: How we handle the rapid growth of the project team title: How we handle the rapid growth of the project team
segments:
- blog
published: true published: true
date: 2021-12-16T11:02:10.123Z date: 2021-12-16T11:02:10.123Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: First Weekly title: First Weekly
segments:
- blog
published: true published: true
date: 2022-02-06T17:13:02.611Z date: 2022-02-06T17:13:02.611Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: "I've started streaming - Weekly #06-2022 " title: "I've started streaming - Weekly #06-2022 "
segments:
- blog
published: true published: true
date: 2022-02-13T19:39:33.578Z date: 2022-02-13T19:39:33.578Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: "Laptop battery got fat - Weekly #07-2022" title: "Laptop battery got fat - Weekly #07-2022"
segments:
- blog
published: true published: true
date: 2022-02-20T17:50:56.214Z date: 2022-02-20T17:50:56.214Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: Error handling with Either<Type> title: Error handling with Either<Type>
segments:
- blog
published: true published: true
date: 2022-02-28T11:30:54.195Z date: 2022-02-28T11:30:54.195Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: "Second attempt @ Weekly #08-2022" title: "Second attempt @ Weekly #08-2022"
segments:
- blog
published: true published: true
date: 2022-02-28T11:49:53.914Z date: 2022-02-28T11:49:53.914Z
tags: tags:

View File

@ -0,0 +1,18 @@
---
layout: blog
title: "DevBreak #1 State of JS 2021 review with Lukáš Orgován"
segments:
- broadcasts
published: true
date: 2022-03-15T20:22:21.191Z
tags:
- DevBreak
---
The first episode of the [DevBreak talk show](/broadcasts/tags/DevBreak).
I've invited my close friend and a tech lead [Lukáš Orgován](https://www.linkedin.com/in/lukasorgovan/) to talk about the opinions on the results of the [State of JS 2021 survey](https://2021.stateofjs.com/en-us/).
This episode aired on the 15th of March 2022 and is in the Slovak language.
<div class="video-embed">
<iframe src="https://player.twitch.tv/?video=1427225407&parent=localhost&parent=michalvanko.dev" frameborder="0" allowfullscreen="true" scrolling="no" height="100%" width="100%" class="embed"></iframe>
</div>

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: "Coming from DevBreak - Weekly #11-2022" title: "Coming from DevBreak - Weekly #11-2022"
segments:
- blog
published: true published: true
date: 2022-03-19T21:22:35.128Z date: 2022-03-19T21:22:35.128Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: "Happy Easter - Weekly #15-2022" title: "Happy Easter - Weekly #15-2022"
segments:
- blog
published: true published: true
date: 2022-04-17T08:05:05.489Z date: 2022-04-17T08:05:05.489Z
tags: tags:

View File

@ -0,0 +1,17 @@
---
layout: blog
title: "DevBreak #2 Stories and impressions from Hack Kosice 2022"
segments:
- broadcasts
published: true
date: 2022-04-26T20:22:21.191Z
tags:
- DevBreak
---
At the last possible moment, I have been able to invite **Daniela, Samuel, and Filip** over to talk about stories and their impressions of the _Hack Kosice 2022_ event that took place last weekend.
This episode aired on the 26th of April 2022 and is in the Slovak language.
<div class="video-embed">
<iframe src="https://player.twitch.tv/?video=1468441353&parent=localhost&parent=michalvanko.dev" frameborder="0" allowfullscreen="true" scrolling="no" height="100%" width="100%" class="embed"></iframe>
</div>

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: 'Treasure hunt - Weekly #18-2022' title: 'Treasure hunt - Weekly #18-2022'
segments:
- blog
published: true published: true
date: 2022-05-07T08:35:51.818Z date: 2022-05-07T08:35:51.818Z
tags: tags:

View File

@ -0,0 +1,17 @@
---
layout: blog
title: "DevBreak #3 Full-stack development with Dominik Štefan"
segments:
- broadcasts
published: true
date: 2022-06-09T20:22:21.191Z
tags:
- DevBreak
---
My friend and former colleague [Dominik Štefan](https://www.linkedin.com/in/dominik-%C5%A1tefan-167266180/) came by to talk about his experience working on full-stack web application development. We talked about many different topics but most importantly we had a lot of fun.
This episode aired on the 9th of June 2022 and is in the Slovak language.
<div class="video-embed">
<iframe src="https://player.twitch.tv/?video=1503339615&parent=localhost&parent=michalvanko.dev" frameborder="0" allowfullscreen="true" scrolling="no" height="100%" width="100%" class="embed"></iframe>
</div>

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: "Our attempt at Rusty game jam - Weekly #25-2022" title: "Our attempt at Rusty game jam - Weekly #25-2022"
segments:
- blog
published: true published: true
date: 2022-06-26T20:02:47.419Z date: 2022-06-26T20:02:47.419Z
tags: tags:

View File

@ -1,6 +1,8 @@
--- ---
layout: blog layout: blog
title: Lovely London Trip title: Lovely London Trip
segments:
- blog
published: true published: true
date: 2022-11-27T19:49:09.204Z date: 2022-11-27T19:49:09.204Z
tags: tags:

View File

@ -0,0 +1,17 @@
---
layout: blog
title: "DevBreak #4 State of JS 2022 review with Filip Seman"
segments:
- broadcasts
published: true
date: 2023-01-21T20:22:21.191Z
tags:
- DevBreak
---
After another year, the results of the [State of JS](https://2022.stateofjs.com/en-us/) survey have been published.
I've invited my #1 fan and friend [Filip Seman](https://www.linkedin.com/in/xseman/).
This episode aired on the 21st of January 2023 and is in the Slovak language.
<div class="video-embed">
<iframe src="https://player.twitch.tv/?video=1715138585&parent=localhost&parent=michalvanko.dev" frameborder="0" allowfullscreen="true" scrolling="no" height="100%" width="100%" class="embed"></iframe>
</div>

View File

@ -0,0 +1,16 @@
---
layout: blog
title: "DevBreak #5 Joys and concerns of Engineering manager with Pavol Dudrík"
segments:
- broadcasts
published: true
date: 2023-02-04T20:22:21.191Z
tags:
- DevBreak
---
In this episode, I've invited my friend [Pavol Dudrík](https://www.linkedin.com/in/pavol-dudr%C3%ADk-043100168/) to talk about what motivates us and makes us happy about the work of the Engineering manager. This job combines two views, and the transition from engineer to manager is often full of pitfalls. We want to share our experience and dispel misconceptions often associated with managerial positions in IT.
This episode aired on the 4th of February 2023 and is in the Slovak language.
<div class="video-embed">
<iframe src="https://player.twitch.tv/?video=1728904048&parent=localhost&parent=michalvanko.dev" frameborder="0" allowfullscreen="true" scrolling="no" height="100%" width="100%" class="embed"></iframe>
</div>

View File

@ -1,6 +1,10 @@
--- ---
layout: blog layout: blog
title: dev - Ide to ? copy title: dev - Ide to ? copy
segments:
- blog
- broadcasts
- cookbook
published: true published: true
date: 2020-01-09T17:24:13.481Z date: 2020-01-09T17:24:13.481Z
thumbnail: /images/uploads/screenshot.gif thumbnail: /images/uploads/screenshot.gif

View File

@ -1,6 +1,10 @@
--- ---
layout: blog layout: blog
title: dev - Ide to ? title: dev - Ide to ?
segments:
- blog
- broadcasts
- cookbook
published: true published: true
date: 2019-08-09T17:24:13.481Z date: 2019-08-09T17:24:13.481Z
thumbnail: /images/uploads/screenshot.gif thumbnail: /images/uploads/screenshot.gif

View File

@ -1,6 +1,10 @@
--- ---
layout: blog layout: blog
title: dev - Anothert one title: dev - Anothert one
segments:
- blog
- broadcasts
- cookbook
published: true published: true
date: 2019-11-03T11:01:32.621Z date: 2019-11-03T11:01:32.621Z
thumbnail: /images/uploads/responzio.png thumbnail: /images/uploads/responzio.png

View File

@ -1,6 +1,10 @@
--- ---
layout: blog layout: blog
title: New Title title: New Title
segments:
- blog
- broadcasts
- cookbook
published: true published: true
date: 2020-05-11T05:38:18.797Z # TODO executable line for generating date date: 2020-05-11T05:38:18.797Z # TODO executable line for generating date
tags: tags:

View File

@ -1,29 +0,0 @@
<section id="personal">
<h3>Personal Information</h3>
<p>I was born on 26th of May in Košice, Slovakia and I still live here.</p>
<h4>Hobbies:</h4>
<p>
I enjoy playing basketball with my friends. I also like to play other team sports like football and hockey.
I also play squash and table tennis. Once I've won a competition in squash at my university.
During summer I love water skiing and swimming in a nearby lake.
<br />
I am very passionate about music. I've also tried some software for composing music but I am not really hooked into that yet.
From time to time I enjoy playing board games with my friends.
</p>
<h4>Interests:</h4>
<p>
I like to explore new technologies and I'm passionate about <em>Open Source movement</em>,
<em>Internet of Things</em> applications and <em>Linux desktop evolution</em>.
<br />
I am interested in modern software architecture and <em>reactive programming</em>.
<br />
I've attended various <strong>tech conferences and hackathons</strong>. I like them for all of the fascinating ideas that might be invented.
<br />
I've given presentations on various topics related to <em>web development</em>. You can <a href='#presentations'>take a look at some of them here</a>.
<br />
I enjoy <strong>teaching and explaining</strong> how various technologies and techniques work to my colleagues for their better understanding.
<br />
I take advantage of <strong>test driven development</strong>.
</p>
</section><!--/personal-->

View File

@ -0,0 +1,38 @@
import { error } from '@sveltejs/kit'
import fm from 'front-matter'
import { readFile } from 'fs'
import { parseField } from '$lib/markdown/parse-markdown'
import { promisify } from 'util'
export interface ArticleAttributes {
slug: string
layout: string
segments: string[]
title: string
published: boolean
date: string
thumbnail: string
tags: string[]
body: string
}
export async function getArticleContent(slug: string) {
let postSource: string
try {
postSource = await promisify(readFile)(`_posts/blog/${slug}.md`, 'utf-8')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
if (e.code === 'ENOENT') {
throw error(404, 'Post not found \n' + e.toString())
}
throw e
}
const parsedPost = fm<ArticleAttributes>(postSource)
const post = parseField<ArticleAttributes>('body')({
...parsedPost.attributes,
body: parsedPost.body,
})
return post
}

View File

@ -8,24 +8,13 @@ import {
filterAndCount, filterAndCount,
type PaginationQuery, type PaginationQuery,
} from '$lib/pagination/pagination' } from '$lib/pagination/pagination'
import type { ArticleAttributes } from './articleContent'
export interface ArticlePreviewAttributes extends ArticleAttributes {
preview: string
}
const { NODE_ENV } = process.env const { NODE_ENV } = process.env
export interface PostAttributes {
layout: string
title: string
published: boolean
date: string
thumbnail: string
tags: string[]
}
export interface PostContent extends PostAttributes {
preview: string
slug: string
published: boolean
}
export async function getBlogListing(paginationQuery: PaginationQuery) { export async function getBlogListing(paginationQuery: PaginationQuery) {
const files = await promisify(readdir)(`_posts/blog/`, 'utf-8') const files = await promisify(readdir)(`_posts/blog/`, 'utf-8')
const filteredFiles = filterDevelopmentFiles(files) const filteredFiles = filterDevelopmentFiles(files)
@ -36,7 +25,7 @@ export async function getBlogListing(paginationQuery: PaginationQuery) {
`_posts/blog/${file}`, `_posts/blog/${file}`,
'utf-8' 'utf-8'
) )
const parsedAttributes = fm<PostAttributes>(fileContent) const parsedAttributes = fm<ArticleAttributes>(fileContent)
const lineOfTextRegExp = /^(?:\w|\[).+/gm const lineOfTextRegExp = /^(?:\w|\[).+/gm
const lines = parsedAttributes.body const lines = parsedAttributes.body
@ -53,7 +42,7 @@ export async function getBlogListing(paginationQuery: PaginationQuery) {
}) })
) )
const filteredContents = pipe( const filteredContents = pipe(
sortBy<PostContent>(prop('date')), sortBy<ArticlePreviewAttributes>(prop('date')),
(items) => reverse(items), (items) => reverse(items),
filter<(typeof contents)[0]>((article) => article.published), filter<(typeof contents)[0]>((article) => article.published),
filterAndCount(paginationQuery) filterAndCount(paginationQuery)

View File

@ -0,0 +1,11 @@
declare global {
interface Window {
onMountScripts?: Array<() => void>
}
}
export function runOnMountScripts() {
window.onMountScripts?.forEach((fn) => {
fn()
})
}

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { format } from 'date-fns' import { format } from 'date-fns'
import type { PostContent } from 'src/routes/blog/content' import type { ArticlePreviewAttributes } from '$lib/articleContent/articleContentListing'
import SvgIcon from './SvgIcon.svelte' import SvgIcon from './SvgIcon.svelte'
import { import {
boldClass, boldClass,
@ -22,7 +22,7 @@
licenceText, licenceText,
} from './Footer.css' } from './Footer.css'
export let latestPosts: PostContent[] export let latestPosts: ArticlePreviewAttributes[]
</script> </script>
<footer class="site-footer navigation-theme {siteFooterClass}"> <footer class="site-footer navigation-theme {siteFooterClass}">
@ -61,7 +61,7 @@
<ul class={listUlClass}> <ul class={listUlClass}>
{#each latestPosts as post} {#each latestPosts as post}
<li class={listLiClass}> <li class={listLiClass}>
<a rel="prefetch" href="/blog/{post.slug}"> <a rel="prefetch" href="/{post.segments[0]}/{post.slug}">
<span>{post.title}</span> <span>{post.title}</span>
<time class="date {dateClass}" datetime={post.date}> <time class="date {dateClass}" datetime={post.date}>
- {format(new Date(post.date), 'do MMM, yyyy')} - {format(new Date(post.date), 'do MMM, yyyy')}

View File

@ -12,40 +12,48 @@
portfolioPageNavigationLinksClass, portfolioPageNavigationLinksClass,
selectedClass, selectedClass,
} from './Nav.css' } from './Nav.css'
import { page } from "$app/stores" import { page } from '$app/stores'
$: segment = $page.url.pathname $: segment = $page.url.pathname
let links = [
{
label: 'Introduction',
url: '/',
},
{
label: 'Blog',
url: '/blog',
},
{
label: 'Broadcasts',
url: '/broadcasts',
},
// {
// label: "Dev's Cookery",
// url: '/cookery',
// },
{
label: 'Portfolio',
url: '/portfolio',
},
]
</script> </script>
<nav class={navigationClass}> <nav class={navigationClass}>
<section class={navigationContentClass}> <section class={navigationContentClass}>
<ul class={navigationLinksClass}> <ul class={navigationLinksClass}>
<li> {#each links as link}
<a class={classNames({ [selectedClass]: segment === '/' })} href="/"> <li>
Introduction <a
</a> rel="prefetch"
</li> class={classNames({ [selectedClass]: segment === link.url })}
<!-- for the blog link, we're using rel=prefetch so that Sapper prefetches href={link.url}
the blog data when we hover over the link or tap it on a touchscreen --> >
<li> {link.label}
<a </a>
rel="prefetch" </li>
class={classNames({ [selectedClass]: segment.startsWith('/blog') })} {/each}
href="/blog"
>
Blog
</a>
</li>
<li>
<a
class={classNames({
[selectedClass]: segment.startsWith('/portfolio'),
})}
href="/portfolio"
>
Portfolio
</a>
</li>
</ul> </ul>
<aside class="logo-section {logoSectionClass}"> <aside class="logo-section {logoSectionClass}">

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import svgSprite from '../svg/build/icons-sprite.svg' import svgSprite from '../../svg/build/icons-sprite.svg'
export let className: string export let className: string
export let name: string export let name: string
</script> </script>

View File

@ -0,0 +1,18 @@
<script lang="ts">
interface ArticleDetails {
title: string
slug: string
preview: string
}
export let segment: string
export let article: ArticleDetails
</script>
<article>
<header>
<h2>
<a rel="prefetch" href={`/${segment}/${article.slug}`}>{article.title}</a>
</h2>
</header>
{@html article.preview}
</article>

View File

@ -2,26 +2,27 @@
import { horizontalBorderTopClass } from '$lib/styles/scoops.css' import { horizontalBorderTopClass } from '$lib/styles/scoops.css'
import { format } from 'date-fns' import { format } from 'date-fns'
import type { PostContent } from '../../routes/blog/content' import type { ArticleContent } from '$lib/content/articleContentListing'
import { import {
footerClass, footerClass,
publishedClass, publishedClass,
publishedLabelClass, publishedLabelClass,
tagsListClass, tagsListClass,
tagsListLiClass, tagsListLiClass,
} from './ArticleFooter.css' } from './ArticlePreviewFooter.css'
export let post: PostContent export let segment: string
export let article: ArticleContent
</script> </script>
<footer class="{footerClass} {horizontalBorderTopClass}"> <footer class="{footerClass} {horizontalBorderTopClass}">
<div class="article-tags"> <div class="article-tags">
{#if post.tags.length > 0} {#if article.tags.length > 0}
<span class="lighten">Tags:</span> <span class="lighten">Tags:</span>
<ul class={tagsListClass}> <ul class={tagsListClass}>
{#each post.tags as tag} {#each article.tags as tag}
<li class={tagsListLiClass}> <li class={tagsListLiClass}>
<a href="/blog/tags/{tag}">{tag}</a> <a href="/{segment}/tags/{tag}">{tag}</a>
</li> </li>
{/each} {/each}
</ul> </ul>
@ -29,8 +30,8 @@
</div> </div>
<div class="created-at"> <div class="created-at">
<span class={publishedLabelClass}>Published on</span> <span class={publishedLabelClass}>Published on</span>
<time datetime={post.date} class={publishedClass}> <time datetime={article.date} class={publishedClass}>
{format(new Date(post.date), "do MMMM',' y")} {format(new Date(article.date), "do MMMM',' y")}
</time> </time>
</div> </div>
</footer> </footer>

View File

@ -0,0 +1,41 @@
<script lang="ts">
import ArticleFooter from '$lib/components/articles/ArticlePreviewFooter/ArticlePreviewFooter.svelte'
import Paginator from '$lib/components/paginator/Paginator.svelte'
import { postListClass } from './ArticlePreviewList.css'
import ArticlePreviewCard from '$lib/components/articles/ArticlePreviewCard/ArticlePreviewCard.svelte'
import type { PaginationResult } from '$lib/pagination/pagination'
import type { ArticleContent } from '$lib/content/articleContentListing'
export let page: number
export let pageSize: number
export let filters: Record<string, string>
export let posts: PaginationResult<ArticleContent>
export let segment: string
</script>
<header>
<Paginator
{segment}
{page}
{pageSize}
{filters}
totalCount={posts.totalCount}
/>
</header>
<ul class="post-list {postListClass}">
{#each posts.items as article (article.slug)}
<li>
<ArticlePreviewCard {article} {segment} />
<ArticleFooter {article} {segment} />
</li>
{/each}
</ul>
<footer>
<Paginator
{segment}
{page}
{pageSize}
{filters}
totalCount={posts.totalCount}
/>
</footer>

View File

@ -10,12 +10,11 @@
export const Divider = 'divider' export const Divider = 'divider'
export let href: string export let segment: string
export let page: number export let page: number
export let pageSize: number export let pageSize: number
export let totalCount: number export let totalCount: number
export let filters: Record<string, string> export let filters: Record<string, string>
let paginatorPages: (number | typeof Divider)[]
$: paginatorPages = getPaginatorPages({ page, pageSize, totalCount }) $: paginatorPages = getPaginatorPages({ page, pageSize, totalCount })
</script> </script>
@ -23,7 +22,7 @@
<ul class={listClass}> <ul class={listClass}>
{#if page !== 1} {#if page !== 1}
<li class="{listItemClass} "> <li class="{listItemClass} ">
<a class={pageLinkClass} href={createHref(href, filters, page - 1)} <a class={pageLinkClass} href={createHref(segment, filters, page - 1)}
>&lt;</a >&lt;</a
> >
</li> </li>
@ -35,7 +34,7 @@
<li class="{listItemClass} {activePage}">{pageNumber}</li> <li class="{listItemClass} {activePage}">{pageNumber}</li>
{:else} {:else}
<li class="{listItemClass} "> <li class="{listItemClass} ">
<a class={pageLinkClass} href={createHref(href, filters, pageNumber)} <a class={pageLinkClass} href={createHref(segment, filters, pageNumber)}
>{pageNumber}</a >{pageNumber}</a
> >
</li> </li>
@ -43,7 +42,7 @@
{/each} {/each}
{#if page !== paginatorPages.length} {#if page !== paginatorPages.length}
<li class="{listItemClass} "> <li class="{listItemClass} ">
<a class={pageLinkClass} href={createHref(href, filters, page + 1)} <a class={pageLinkClass} href={createHref(segment, filters, page + 1)}
>&gt;</a >&gt;</a
> >
</li> </li>

View File

@ -11,11 +11,13 @@ export function getPaginatorPages({
page: number page: number
pageSize: number pageSize: number
totalCount: number totalCount: number
}) { }): (number | typeof Divider)[] {
const maxLinksLength = 7 const maxLinksLength = 7
const linksAroundActive = 2 const linksAroundActive = 2
const totalPages = Math.ceil(totalCount / pageSize) const totalPages = Math.ceil(totalCount / pageSize)
const daco = range(1, totalPages + 1).reduce((acc, link) => { const shownPages = range(1, totalPages + 1).reduce<
(number | typeof Divider)[]
>((acc, link) => {
const isFirst = link === 1 const isFirst = link === 1
const isLast = link === totalPages const isLast = link === totalPages
const isPageOnStart = page <= 3 && link < maxLinksLength const isPageOnStart = page <= 3 && link < maxLinksLength
@ -36,7 +38,7 @@ export function getPaginatorPages({
return [...acc, link] return [...acc, link]
}, []) }, [])
return daco return shownPages
} }
export function createHref( export function createHref(

View File

@ -1,9 +1,9 @@
import { splitEvery } from 'ramda' import { init, splitEvery } from 'ramda'
export function parseParams(params: string) { export function parseParams(params: string) {
const splittedParams = params.split('/') let splittedParams = params.split('/')
if (splittedParams.length % 2 !== 0) { if (splittedParams.length % 2 !== 0) {
return [] splittedParams = init(splittedParams)
} }
const splits = splitEvery(2, splittedParams) const splits = splitEvery(2, splittedParams)
return Object.fromEntries(splits) return Object.fromEntries(splits)

View File

@ -17,7 +17,10 @@ export function dropAndTake<Item>({ offset = 0, limit = Infinity }) {
) => Item[] ) => Item[]
} }
export function filterByPropContains<Item>(filters: Record<string, string>) { // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function filterByPropContains<Item extends Record<string, any>>(
filters: Record<string, string>
) {
return function (items: Item[]) { return function (items: Item[]) {
return items.filter((item) => { return items.filter((item) => {
return Object.entries(filters).every(([fieldName, value]) => return Object.entries(filters).every(([fieldName, value]) =>
@ -27,7 +30,8 @@ export function filterByPropContains<Item>(filters: Record<string, string>) {
} }
} }
export function filterAndCount<Item>({ // eslint-disable-next-line @typescript-eslint/no-explicit-any
export function filterAndCount<Item extends Record<string, any>>({
filters, filters,
...dropTakeParams ...dropTakeParams
}: PaginationQuery) { }: PaginationQuery) {

View File

@ -19,3 +19,9 @@ globalStyle(`${contentClass} img:only-child`, {
display: 'block', display: 'block',
margin: '0 auto', margin: '0 auto',
}) })
globalStyle(`${contentClass} .video-embed`, {
margin: '0 auto',
maxWidth: vars.width.image,
aspectRatio: vars.aspectRatio.monitor,
})

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import type { LayoutData } from "./$types" import type { LayoutData } from './$types'
import Nav from '../components/Nav.svelte' import Nav from '$lib/components/Nav.svelte'
import Footer from '../components/Footer.svelte' import Footer from '$lib/components/Footer.svelte'
import 'modern-normalize/modern-normalize.css' import 'modern-normalize/modern-normalize.css'
import '$lib/styles/global.css' import '$lib/styles/global.css'
import { mainContentClass } from './layout.css' import { mainContentClass } from './layout.css'

View File

@ -2,7 +2,7 @@ import type { LayoutLoad } from './$types'
export const prerender = true export const prerender = true
export const load = (async ({ fetch }) => { export const load = (async ({ fetch }) => {
const blogPostsResponse = await fetch(`/blog/articles/pageSize/5.json`) const blogPostsResponse = await fetch(`/articles/pageSize/5.json`)
const blogPostsContent = await blogPostsResponse.json() const blogPostsContent = await blogPostsResponse.json()
return { return {

View File

@ -3,7 +3,7 @@ import {
parseParams, parseParams,
} from '$lib/pagination/dropTakeParams' } from '$lib/pagination/dropTakeParams'
import { json } from '@sveltejs/kit' import { json } from '@sveltejs/kit'
import { getBlogListing } from '../../content' import { getBlogListing } from '$lib/articleContent/articleContentListing'
import type { RequestHandler } from './$types' import type { RequestHandler } from './$types'
export const prerender = true export const prerender = true

View File

@ -1,12 +1,10 @@
<script lang="ts"> <script lang="ts">
import ArticleFooter from '../../../components/blog/ArticleFooter.svelte'
import Paginator from '../../../components/paginator/Paginator.svelte'
import { postListClass, seeAllClass } from './page.css'
import type { PageData } from './$types' import type { PageData } from './$types'
import ArticlePreviewList from '$lib/components/articles/ArticlePreviewList/ArticlePreviewList.svelte'
import { seeAllClass } from '$lib/components/articles/ArticlePreviewList/ArticlePreviewList.css'
export let data: PageData export let data: PageData
let { posts, filters, page, pageSize } = data $: ({ posts, filters } = data)
$: ({ posts, filters, page, pageSize } = data)
</script> </script>
<svelte:head> <svelte:head>
@ -30,36 +28,5 @@
</div> </div>
{/if} {/if}
{/if} {/if}
<header>
<Paginator <ArticlePreviewList {...data} segment="blog" />
href="blog"
{page}
{pageSize}
{filters}
totalCount={posts.totalCount}
/>
</header>
<ul class="post-list {postListClass}">
{#each posts.items as post (post.slug)}
<li>
<article>
<header>
<h2>
<a rel="prefetch" href="/blog/{post.slug}">{post.title}</a>
</h2>
</header>
{@html post.preview}
</article>
<ArticleFooter {post} />
</li>
{/each}
</ul>
<footer>
<Paginator
href="blog"
{page}
{pageSize}
{filters}
totalCount={posts.totalCount}
/>
</footer>

View File

@ -1,16 +1,15 @@
import { parseParams } from '$lib/pagination/dropTakeParams' import { parseParams } from '$lib/pagination/dropTakeParams'
import type { PageLoad } from './$types' import type { PageLoad } from './$types'
import type { PostContent } from './../content' import type { ArticlePreviewAttributes } from '$lib/articleContent/articleContentListing'
import type { PaginationResult } from '$lib/pagination/pagination' import type { PaginationResult } from '$lib/pagination/pagination'
export const load = (async ({ fetch, params }) => { export const load = (async ({ fetch, params }) => {
const { page = 1, pageSize = 7, ...filters } = parseParams(params.params) const { page = 1, pageSize = 7, ...filters } = parseParams(params.params)
const articleResponse = await fetch( const articleResponse = await fetch(
`/blog/articles/${params.params ? params.params : 'index'}.json` `/articles/segments/blog${params.params ? `/${params.params}` : ''}.json`
).then((r) => r.json()) ).then((r) => r.json())
return { return {
posts: articleResponse.posts as PaginationResult<PostContent>, posts: articleResponse.posts as PaginationResult<ArticlePreviewAttributes >,
page: Number(page), page: Number(page),
pageSize, pageSize,
filters, filters,

View File

@ -1,35 +1,9 @@
import { readFile } from 'fs'
import { promisify } from 'util'
import fm from 'front-matter'
import { parseField } from '../../../markdown/parse-markdown'
import { error, json } from '@sveltejs/kit'
import type { PostAttributes } from '../content'
import type { PageServerLoad } from './$types' import type { PageServerLoad } from './$types'
import { getArticleContent } from '$lib/articleContent/articleContent'
export const prerender = true export const prerender = true
export interface SinglePost {
body: string
}
export const load = (async ({ params: { slug } }) => { export const load = (async ({ params: { slug } }) => {
let postSource: string const post = await getArticleContent(slug);
try {
postSource = await promisify(readFile)(`_posts/blog/${slug}.md`, 'utf-8')
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
if (e.code === 'ENOENT') {
throw error(404, 'Post not found \n' + e.toString())
}
throw e
}
const parsedPost = fm<PostAttributes>(postSource)
const post = parseField<SinglePost>('body')({
...parsedPost.attributes,
body: parsedPost.body,
})
return post return post
}) satisfies PageServerLoad }) satisfies PageServerLoad

View File

@ -1,9 +1,15 @@
<script lang="ts"> <script lang="ts">
import ArticleFooter from '../../../components/blog/ArticleFooter.svelte' import ArticleFooter from '$lib/components/articles/ArticlePreviewFooter/ArticlePreviewFooter.svelte'
import type { PageData } from './$types' import type { PageData } from './$types'
import { contentClass } from './page.css' import { contentClass } from '$lib/styles/article/article.css'
import { onMount } from 'svelte'
import { runOnMountScripts } from '$lib/articleContent/onMountScripts'
export let data: PageData export let data: PageData
onMount(() => {
runOnMountScripts()
})
</script> </script>
<svelte:head> <svelte:head>
@ -12,7 +18,7 @@
<h1>{data.title}</h1> <h1>{data.title}</h1>
<div class="content {contentClass}"> <article class="content {contentClass}">
{@html data.body} {@html data.body}
</div> </article>
<ArticleFooter post={data} /> <ArticleFooter article={data} segment="blog" />

View File

@ -0,0 +1,30 @@
<script lang="ts">
import type { PageData } from './$types'
import ArticlePreviewList from '$lib/components/articles/ArticlePreviewList/ArticlePreviewList.svelte'
import { seeAllClass } from '$lib/components/articles/ArticlePreviewList/ArticlePreviewList.css'
export let data: PageData
$: ({ posts, filters } = data)
</script>
<svelte:head>
<title>Broadcasts @michalvankodev</title>
</svelte:head>
{#if posts.items.length === 0}
<p class="no-posts">You've found void in the space.</p>
{:else}
<h1>
{#if filters.tags}
<em>{filters.tags}</em>
{/if}
Broadcasts
</h1>
{#if filters.tags}
<div class={seeAllClass}>
<a href="/broadcasts">See all broadcasts</a>
</div>
{/if}
{/if}
<ArticlePreviewList {...data} segment="broadcasts" />

View File

@ -0,0 +1,18 @@
import { parseParams } from '$lib/pagination/dropTakeParams'
import type { PageLoad } from './$types'
import type { ArticlePreviewAttributes } from '$lib/articleContent/articleContentListing'
import type { PaginationResult } from '$lib/pagination/pagination'
export const load = (async ({ fetch, params }) => {
const { page = 1, pageSize = 7, ...filters } = parseParams(params.params)
const articleResponse = await fetch(
`/articles/segments/broadcasts${params.params ? `/${params.params}` : ''}.json`
).then((r) => r.json())
return {
posts: articleResponse.posts as PaginationResult<ArticlePreviewAttributes>,
page: Number(page),
pageSize,
filters,
}
}) satisfies PageLoad

View File

@ -0,0 +1,9 @@
import type { PageServerLoad } from './$types'
import { getArticleContent } from '$lib/articleContent/articleContent'
export const prerender = true
export const load = (async ({ params: { slug } }) => {
const post = await getArticleContent(slug);
return post
}) satisfies PageServerLoad

View File

@ -0,0 +1,24 @@
<script lang="ts">
import ArticleFooter from '$lib/components/articles/ArticlePreviewFooter/ArticlePreviewFooter.svelte'
import type { PageData } from './$types'
import { contentClass } from '$lib/styles/article/article.css'
import { onMount } from 'svelte'
import { runOnMountScripts } from '$lib/articleContent/onMountScripts'
export let data: PageData
onMount(() => {
runOnMountScripts()
})
</script>
<svelte:head>
<title>{data.title}</title>
</svelte:head>
<h1>{data.title}</h1>
<article class="content {contentClass}">
{@html data.body}
</article>
<ArticleFooter article={data} segment="broadcasts" />

View File

@ -1,5 +1,5 @@
import { getBlogListing } from '$lib/articleContent/articleContentListing'
import { Feed } from 'feed' import { Feed } from 'feed'
import { getBlogListing } from './blog/content'
export async function getFeed() { export async function getFeed() {
const feed = new Feed({ const feed = new Feed({

View File

@ -3,7 +3,7 @@ import { promisify } from 'util'
import fm from 'front-matter' import fm from 'front-matter'
// TODO Switch marked for unified // TODO Switch marked for unified
import marked from 'marked' import marked from 'marked'
import { parseField } from '../../markdown/parse-markdown' import { parseField } from '$lib/markdown/parse-markdown'
import type { PageServerLoad } from './$types' import type { PageServerLoad } from './$types'
export const prerender = true export const prerender = true

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import Work from '../../components/portfolio/work.svelte' import Work from './components/work.svelte'
import Project from '../../components/portfolio/project.svelte' import Project from './components/project.svelte'
import Presentation from '../../components/portfolio/presentation.svelte' import Presentation from './components/presentation.svelte'
import type { PageData } from './$types' import type { PageData } from './$types'
import { listClass, listItemClass, nameTagClass } from './page.css' import { listClass, listItemClass, nameTagClass } from './page.css'

View File

@ -0,0 +1,39 @@
<section id="personal">
<h3>Personal Information</h3>
<p>I was born on 26th of May in Košice, Slovakia and I still live here.</p>
<h4>Hobbies:</h4>
<p>
I enjoy playing basketball with my friends. I also like to play other team
sports like football and hockey. I also play squash and table tennis. Once
I've won a competition in squash at my university. During summer I love
water skiing and swimming in a nearby lake.
<br />
I am very passionate about music. I've also tried some software for composing
music but I am not really hooked into that yet. From time to time I enjoy playing
board games with my friends.
</p>
<h4>Interests:</h4>
<p>
I like to explore new technologies and I'm passionate about <em
>Open Source movement</em
>,
<em>Internet of Things</em> applications and
<em>Linux desktop evolution</em>.
<br />
I am interested in modern software architecture and
<em>reactive programming</em>.
<br />
I've attended various <strong>tech conferences and hackathons</strong>. I
like them for all of the fascinating ideas that might be invented.
<br />
I've given presentations on various topics related to
<em>web development</em>. You can
<a href="#presentations">take a look at some of them here</a>.
<br />
I enjoy <strong>teaching and explaining</strong> how various technologies
and techniques work to my colleagues for their better understanding.
<br />
I take advantage of <strong>test driven development</strong>.
</p>
</section>
<!--/personal-->

View File

@ -19,6 +19,15 @@ collections:
fields: # The fields for each document, usually in front matter fields: # The fields for each document, usually in front matter
- { label: 'Layout', name: 'layout', widget: 'hidden', default: 'blog' } - { label: 'Layout', name: 'layout', widget: 'hidden', default: 'blog' }
- { label: 'Title', name: 'title', widget: 'string' } - { label: 'Title', name: 'title', widget: 'string' }
- label: 'Segments'
name: 'segments'
widget: 'select'
multiple: true
options:
- { label: 'Blog', value: 'blog' }
- { label: 'Broadcasts', value: 'broadcasts' }
- { label: 'Cookbook', value: 'cookbook' }
default: ['blog']
- { - {
label: 'Published', label: 'Published',
name: 'published', name: 'published',