What a refactor of articles

This commit is contained in:
Michal Vanko 2023-02-12 14:56:49 +01:00
parent 33bf6769e4
commit f156d4dacc
35 changed files with 228 additions and 134 deletions

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

@ -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 { ArticleContent } from '$lib/content/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: ArticleContent[]
</script> </script>
<footer class="site-footer navigation-theme {siteFooterClass}"> <footer class="site-footer navigation-theme {siteFooterClass}">

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="/">
Introduction
</a>
</li>
<!-- for the blog link, we're using rel=prefetch so that Sapper prefetches
the blog data when we hover over the link or tap it on a touchscreen -->
<li> <li>
<a <a
rel="prefetch" rel="prefetch"
class={classNames({ [selectedClass]: segment.startsWith('/blog') })} class={classNames({ [selectedClass]: segment === link.url })}
href="/blog" href={link.url}
> >
Blog {link.label}
</a>
</li>
<li>
<a
class={classNames({
[selectedClass]: segment.startsWith('/portfolio'),
})}
href="/portfolio"
>
Portfolio
</a> </a>
</li> </li>
{/each}
</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,11 @@ 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 daco = 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

View File

@ -11,7 +11,7 @@ import {
const { NODE_ENV } = process.env const { NODE_ENV } = process.env
export interface PostAttributes { export interface ArticleAttributes {
layout: string layout: string
title: string title: string
published: boolean published: boolean
@ -20,7 +20,7 @@ export interface PostAttributes {
tags: string[] tags: string[]
} }
export interface PostContent extends PostAttributes { export interface ArticleContent extends ArticleAttributes {
preview: string preview: string
slug: string slug: string
published: boolean published: boolean
@ -36,7 +36,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 +53,7 @@ export async function getBlogListing(paginationQuery: PaginationQuery) {
}) })
) )
const filteredContents = pipe( const filteredContents = pipe(
sortBy<PostContent>(prop('date')), sortBy<ArticleContent>(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

@ -17,7 +17,8 @@ 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 +28,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

@ -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/content/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,16 @@
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 { ArticleContent } from '$lib/content/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/${params.params ? params.params : 'index'}.json`
).then((r) => r.json()) ).then((r) => r.json())
return { return {
posts: articleResponse.posts as PaginationResult<PostContent>, posts: articleResponse.posts as PaginationResult<ArticleContent>,
page: Number(page), page: Number(page),
pageSize, pageSize,
filters, filters,

View File

@ -2,8 +2,8 @@ import { readFile } from 'fs'
import { promisify } from 'util' import { promisify } from 'util'
import fm from 'front-matter' import fm from 'front-matter'
import { parseField } from '../../../markdown/parse-markdown' import { parseField } from '../../../markdown/parse-markdown'
import { error, json } from '@sveltejs/kit' import { error } from '@sveltejs/kit'
import type { PostAttributes } from '../content' import type { ArticleAttributes } from '$lib/content/articleContentListing'
import type { PageServerLoad } from './$types' import type { PageServerLoad } from './$types'
export const prerender = true export const prerender = true
@ -24,7 +24,7 @@ export const load = (async ({ params: { slug } }) => {
throw e throw e
} }
const parsedPost = fm<PostAttributes>(postSource) const parsedPost = fm<ArticleAttributes>(postSource)
const post = parseField<SinglePost>('body')({ const post = parseField<SinglePost>('body')({
...parsedPost.attributes, ...parsedPost.attributes,

View File

@ -1,5 +1,5 @@
<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 './page.css'
@ -15,4 +15,4 @@
<div class="content {contentClass}"> <div class="content {contentClass}">
{@html data.body} {@html data.body}
</div> </div>
<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 { ArticleContent } from '$lib/content/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/${params.params ? params.params : 'index'}.json`
).then((r) => r.json())
return {
posts: articleResponse.posts as PaginationResult<ArticleContent>,
page: Number(page),
pageSize,
filters,
}
}) satisfies PageLoad

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