-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathbiteSized.v2.tsx
188 lines (184 loc) · 7.78 KB
/
biteSized.v2.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import * as React from 'react'
import { Store } from 'redux'
import { State, hasTag, getConfig, Selector } from './store'
import { toVFile, read } from './files'
import { join } from 'path'
import { Html, Head, OpenGraph, Body, Footer, Navbar, Subscribe, Author, WhatIsLearnk8s } from './layout.v3'
import { JsonLd } from 'react-schemaorg'
import { BlogPosting } from 'schema-dts'
import { renderToJsx, toMdast } from './markdown'
import { defaultAssetsPipeline } from './optimise'
import { Page } from './store/websiteReducer'
import { format } from 'date-fns'
import { selectAll } from 'unist-util-select'
import * as Mdast from 'mdast'
import { transform } from './markdown/utils'
import { mdast2Jsx, mdast2JsxInline } from './markdown/jsx'
import { tachyons } from './tachyons/tachyons'
export async function Mount({ store }: { store: Store }) {
const state = store.getState()
const pages = Selector.pages.selectAll(state).filter(hasTag(state, 'bite-sized'))
await Promise.all(
pages.map(async page => {
defaultAssetsPipeline({
jsx: await renderPage(page, state),
isOptimisedBuild: getConfig(state).isProduction,
siteUrl: `${getConfig(state).protocol}://${getConfig(state).hostname}`,
url: page.url,
outputFolder: getConfig(state).outputFolder,
})
}),
)
}
async function renderPage(page: Page, state: State) {
const openGraph = Selector.openGraphs.selectAll(state).find(it => it.pageId === page.id)
if (!openGraph) {
throw new Error('The page does not have an open graph.')
}
const blog = Selector.blogPosts.selectAll(state).find(it => it.pageId === page.id)
if (!blog) {
throw new Error('The page is not a blog post page.')
}
const author = Selector.authors.selectAll(state).find(it => it.id === blog.authorId)
if (!author) {
throw new Error('The blog post does not have an author attached')
}
const previewPicture = Selector.previewPictures.selectAll(state).find(it => it.pageId === page.id)
const extraBlocks = Selector.relatedBlogs.selectAll(state).filter(it => it.blogPostId === blog.id)
const currentAbsoluteUrl = `${getConfig(state).protocol}://${join(getConfig(state).hostname, page.url)}`
const [content, ...blocks] = await Promise.all([
read(blog.content),
...extraBlocks.map(it => it.content).map(it => read(it)),
])
return (
<Html>
<Head title={page.title} description={page.description}>
{openGraph ? (
<OpenGraph
title={openGraph.title}
description={openGraph.description}
image={openGraph.imagePath}
currentAbsoluteUrl={currentAbsoluteUrl}
/>
) : null}
<style>{tachyons}</style>
<link rel='stylesheet' href='assets/style.css' />
<link rel='canonical' href={currentAbsoluteUrl} />
<JsonLd<BlogPosting>
item={{
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: blog.title,
image: `${openGraph.imagePath}`,
author: {
'@type': 'Person',
name: author.fullName,
},
publisher: {
'@type': 'Organization',
name: 'Learnk8s',
logo: {
'@type': 'ImageObject',
url: `assets/learnk8s_logo_square.png`,
},
},
url: currentAbsoluteUrl,
datePublished: blog.publishedDate,
dateModified: blog.lastModifiedDate || blog.publishedDate,
description: blog.description,
mainEntityOfPage: {
'@type': 'SoftwareSourceCode',
},
}}
/>
</Head>
<Body>
<div className='white mb4 mb5-ns'>
<Navbar />
</div>
<div className='tc mb4 db mw4 center'>
<Author name={author.fullName} avatar={author.avatar} link={author.link} />
</div>
<article className='lazy-article ph3 pt0 pb4 mw7 center'>
<h1 className='navy tc f2 f1-ns'>{blog.title}</h1>
<p className='f7 black-60 tc ttu'>Published in {format(new Date(blog.publishedDate), 'MMMM yyyy')}</p>
{blog.lastModifiedDate ? (
<p className='f7 black-60 tc ttu b'>
<Tick className='w1 h1 v-mid' /> Updated in {format(new Date(blog.lastModifiedDate), 'MMMM yyyy')}
</p>
) : null}
<hr className='pv2 bn' />
<div className='aspect-ratio aspect-ratio--6x4'>
{previewPicture ? (
<img src={previewPicture.imagePath} className='aspect-ratio--object' alt={page.title} />
) : (
<img src='assets/bsk.svg' className='aspect-ratio--object' alt={blog.title} />
)}
</div>
<hr className='w3 center b--navy mv4 mb5-ns' />
<p className='lh-copy measure-wide f4'>
<strong className='b'>Welcome to Bite-sized Kubernetes learning</strong> — a regular column on the most
interesting questions that we see online and during our workshops answered by a Kubernetes expert.
</p>
<blockquote className='pl3 mh2 bl bw2 b--blue bg-evian pv1 ph4'>
<p className='lh-copy measure-wide f4'>
Today's answers are curated by{' '}
<a href={author.link} className='link navy underline hover-sky' target='_blank' rel='noreferrer'>
{author.fullName}
</a>
. {transform(toMdast(toVFile({ contents: author.description || '' })), mdast2JsxInline())}
</p>
</blockquote>
<p className='lh-copy measure-wide f4'>
<em className='i'>
If you wish to have your question featured on the next episode,{' '}
<a href='mailto:hello@learnk8s.io' className='link navy underline hover-sky' target='_self'>
please get in touch via email
</a>{' '}
or{' '}
<a
href='https://twitter.com/learnk8s'
className='link navy underline hover-sky'
target='_blank'
rel='noreferrer'
>
you can tweet us at @learnk8s
</a>
.
</em>
</p>
<p className='lh-copy measure-wide f4'>
Did you miss the previous episodes?{' '}
<a href='/bite-sized' className='link navy underline hover-sky'>
You can find them here.
</a>
</p>
{renderToJsx(content)}
{blocks.map(it => {
const mdast = toMdast(it)
bumpHeadings(mdast.children, 1)
return transform({ type: 'root' as const, children: mdast.children }, mdast2Jsx())
})}
<Subscribe identifier={blog.id}></Subscribe>
</article>
<WhatIsLearnk8s />
<Footer />
</Body>
</Html>
)
}
function bumpHeadings(children: Mdast.Content[], amount: number): void {
selectAll<Mdast.Heading>('heading', { type: 'root', children }).forEach(heading => {
heading.depth = heading.depth + amount
})
}
const Tick: React.StatelessComponent<{ className?: string }> = ({ children, className }) => {
return (
<svg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg' className={className || ''}>
<g fill='#3BDBB8' fill-rule='evenodd'>
<circle fill-opacity='.2' cx='15' cy='15' r='15' />
<path d='M22.61 12.116c0 .235-.094.47-.263.64l-8.099 8.098a.913.913 0 0 1-1.28 0l-4.69-4.69a.913.913 0 0 1 0-1.28l1.28-1.28a.913.913 0 0 1 1.281 0l2.77 2.777 6.177-6.186a.913.913 0 0 1 1.28 0l1.28 1.28c.17.17.265.405.265.64z' />
</g>
</svg>
)
}