Typescript support

This commit is contained in:
2020-09-26 14:49:58 +02:00
parent 57ed783083
commit dcfc3eccc2
22 changed files with 3132 additions and 1264 deletions

View File

@ -1,7 +1,8 @@
<script>
<script lang="typescript">
import { format } from 'date-fns'
import type { PostContent } from '../../routes/blog/_content';
export let post
export let post: PostContent
</script>
<style>

View File

@ -1,5 +1,7 @@
<script>
export let project
<script lang="typescript">
import type { ProjectAttributes } from "../../routes/portfolio/index.json";
export let project: ProjectAttributes
</script>
<style>

View File

@ -1,7 +1,7 @@
import marked from 'marked'
export function parseField(field) {
return item => ({
export function parseField<T>(field: string) {
return (item: T) => ({
...item,
[field]: marked(item[field]),
})

View File

@ -2,13 +2,18 @@ import { readFile } from 'fs'
import { promisify } from 'util'
import fm from 'front-matter'
import { parseField } from '../../markdown/parse-markdown'
import type { PostAttributes } from './_content'
export interface SinglePost {
body: string
}
export async function get(req, res, next) {
// the `slug` parameter is available because
// this file is called [slug].json.js
const { slug } = req.params
let postSource
let postSource: string
try {
postSource = await promisify(readFile)(`_posts/blog/${slug}.md`, 'utf-8')
} catch (e) {
@ -22,9 +27,9 @@ export async function get(req, res, next) {
return
}
const parsedPost = fm(postSource)
const parsedPost = fm<PostAttributes>(postSource)
const response = parseField('body')({
const response = parseField<SinglePost>('body')({
...parsedPost.attributes,
body: parsedPost.body,
})

View File

@ -64,8 +64,8 @@
<svelte:head>
<title>{post.title}</title>
<link rel="stylesheet" href="prism.css" />
<script src="prism.js">
<link rel="stylesheet" href="/prism.css" />
<script src="../../../static/prism.js" defer>
</script>
</svelte:head>

View File

@ -7,17 +7,32 @@ import marked from 'marked'
const { NODE_ENV } = process.env
export async function getBlogListing(tag) {
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(tag?: string) {
const files = await promisify(readdir)(`_posts/blog/`, 'utf-8')
const filteredFiles = filterDevelopmentFiles(files)
const contents = await Promise.all(
filteredFiles.map(async file => {
filteredFiles.map(async (file) => {
const fileContent = await promisify(readFile)(
`_posts/blog/${file}`,
'utf-8'
)
const parsedAttributes = fm(fileContent)
const parsedAttributes = fm<PostAttributes>(fileContent)
const lineOfTextRegExp = /^(?:\w|\[).+/gm
const lines = parsedAttributes.body
@ -33,23 +48,30 @@ export async function getBlogListing(tag) {
}
})
)
const filteredContents = pipe(
const filteredContents = pipe<
PostContent[],
PostContent[],
PostContent[],
PostContent[],
PostContent[]
>(
sortBy(prop('date')),
reverse,
filter(article => article.published),
filter<typeof contents[0]>((article) => article.published),
partial(filterByTag, [tag])
)(contents)
return filteredContents
}
function filterDevelopmentFiles(files) {
function filterDevelopmentFiles(files: string[]) {
return NODE_ENV !== 'production'
? files
: files.filter(file => !file.startsWith('dev-'))
: files.filter((file) => !file.startsWith('dev-'))
}
function filterByTag(tag, contents) {
return tag ? contents.filter(content => content.tags.includes(tag)) : contents
function filterByTag(tag: string | undefined, contents: PostContent[]) {
return tag
? contents.filter((content) => content.tags.includes(tag))
: contents
}

View File

@ -1,4 +1,4 @@
<script context="module">
<script context="module" lang="typescript">
export function preload({ params, query }) {
const blogQuery = query
? '?' +
@ -14,11 +14,12 @@
}
</script>
<script>
<script lang="typescript">
import ArticleFooter from '../../components/blog/article-footer.svelte'
import type { PostContent } from './_content';
export let posts
export let query
export let posts: PostContent[]
export let query
</script>
<style>

View File

@ -25,14 +25,16 @@ export async function getFeed() {
})
const blogListing = await getBlogListing()
blogListing.forEach(post => {
blogListing.forEach((post) => {
feed.addItem({
title: post.title,
id: `https://michalvanko.dev/blog/${post.slug}`,
link: `https://michalvanko.dev/blog/${post.slug}`,
description: post.preview,
date: post.date,
image: post.thumbnail ? `https://michalvanko.dev/${post.thumbnail}` : undefined,
date: new Date(post.date),
image: post.thumbnail
? `https://michalvanko.dev/${post.thumbnail}`
: undefined,
})
})
return feed

View File

@ -1,11 +1,10 @@
import { getFeed } from './_feed'
export async function get(req, res) {
const feed = await getFeed()
const feed = await getFeed()
res.writeHead(200, {
'Content-Type': 'application/json',
})
res.end(feed.json1())
}

View File

@ -1,11 +1,10 @@
import { getFeed } from './_feed'
export async function get(req, res) {
const feed = await getFeed()
const feed = await getFeed()
res.writeHead(200, {
'Content-Type': 'application/xml',
})
res.end(feed.rss2())
}

View File

@ -4,8 +4,26 @@ import fm from 'front-matter'
import marked from 'marked'
import { parseField } from '../../markdown/parse-markdown'
export interface RecordAttributes {
name: string
description: string
displayed: boolean
}
export interface ProjectAttributes extends RecordAttributes {
image: string
}
export interface PortfolioAttributes {
title: string
work_history_prelude: string
work_history: string[]
projects: ProjectAttributes[]
education: RecordAttributes[]
}
export async function get(req, res, next) {
let pageSource
let pageSource: string
try {
pageSource = await promisify(readFile)('_pages/portfolio.md', 'utf-8')
} catch (e) {
@ -14,15 +32,15 @@ export async function get(req, res, next) {
return
}
const parsed = fm(pageSource)
const parsed = fm<PortfolioAttributes>(pageSource)
const workHistory = (parsed.attributes.work_history || []).map(
parseField('description')
)
const projects = (parsed.attributes.projects || [])
.filter(project => project.displayed)
.filter((project) => project.displayed)
.map(parseField('description'))
const education = (parsed.attributes.education || [])
.filter(education => education.displayed)
.filter((education) => education.displayed)
.map(parseField('description'))
const response = {

View File

@ -1,17 +0,0 @@
import sirv from 'sirv';
import polka from 'polka';
import compression from 'compression';
import * as sapper from '@sapper/server';
const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';
polka() // You can also use Express
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
sapper.middleware()
)
.listen(PORT, err => {
if (err) console.log('error', err);
});

17
src/server.ts Normal file
View File

@ -0,0 +1,17 @@
import sirv from 'sirv'
import polka from 'polka'
import compression from 'compression'
import * as sapper from '@sapper/server'
const { PORT, NODE_ENV } = process.env
const dev = NODE_ENV === 'development'
polka() // You can also use Express
.use(
compression({ threshold: 0 }),
sirv('static', { dev }),
sapper.middleware()
)
.listen(PORT, (err) => {
if (err) console.log('error', err)
})

View File

@ -28,15 +28,16 @@
<!-- This contains the contents of the <svelte:head> component, if
the current page has one -->
%sapper.head%
<!-- Sapper creates a <script> tag containing `app/client.js`
and anything else it needs to hydrate the app and
initialise the router -->
%sapper.scripts%
</head>
<body>
<!-- The application will be rendered inside this element,
because `app/client.js` references it -->
<div id="sapper">%sapper.html%</div>
<!-- Sapper creates a <script> tag containing `app/client.js`
and anything else it needs to hydrate the app and
initialise the router -->
%sapper.scripts%
</body>
</html>