Typescript support
This commit is contained in:
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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]),
|
||||
})
|
@ -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,
|
||||
})
|
@ -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>
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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
|
@ -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())
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
|
@ -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 = {
|
@ -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
17
src/server.ts
Normal 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)
|
||||
})
|
@ -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>
|
||||
|
Reference in New Issue
Block a user