mirror of
https://github.com/mdx-js/mdx.git
synced 2024-09-11 15:05:32 +03:00
Add a nice blog page (#1719)
Reviewed-by: Christian Murphy <christian.murphy.42@gmail.com>
This commit is contained in:
parent
4a3261be56
commit
7b72f6ff33
@ -554,6 +554,23 @@ button:focus {
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: block;
|
||||
padding: calc(1 * (1em + 1ex));
|
||||
margin: calc(2 * (1em + 1ex)) 0;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 0 0.2em rgba(3, 102, 214, 0),
|
||||
0 13px 27px -5px rgba(50, 50, 93, 0.25), 0 8px 16px -8px rgba(0, 0, 0, 0.3),
|
||||
0 -6px 16px -6px rgba(0, 0, 0, 0.025);
|
||||
background-image: radial-gradient(
|
||||
ellipse at 100% 100%,
|
||||
rgb(252 180 45 / 10%) 20%,
|
||||
transparent 80%
|
||||
);
|
||||
}
|
||||
|
||||
#markdown-for-thecomponent-era {
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -573,11 +590,11 @@ button:focus {
|
||||
padding: calc(1em + 1ex);
|
||||
}
|
||||
|
||||
.home-preview > :is(h1, h2, h3, h4):first-child {
|
||||
:is(.home-preview, .card) > :is(h1, h2, h3, h4, p, .block):first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.home-preview > :is(h1, h2, h3, h4):last-child {
|
||||
:is(.home-preview, .card) > :is(h1, h2, h3, h4, p, .block):last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@ -841,6 +858,15 @@ button:focus {
|
||||
background-color: var(--hl);
|
||||
color: var(--gray-0);
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--gray-9);
|
||||
background-image: radial-gradient(
|
||||
ellipse at 100% 100%,
|
||||
rgb(252 180 45 / 10%) 20%,
|
||||
transparent 80%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 22em) {
|
||||
|
75
docs/_component/blog.server.js
Normal file
75
docs/_component/blog.server.js
Normal file
@ -0,0 +1,75 @@
|
||||
import React, {createElement} from 'react'
|
||||
import apStyleTitleCase from 'ap-style-title-case'
|
||||
import {toH} from 'hast-to-hyperscript'
|
||||
import {sortItems} from './sort.js'
|
||||
|
||||
const dateTimeFormat = new Intl.DateTimeFormat('en')
|
||||
|
||||
export const BlogEntry = (props) => {
|
||||
const {item} = props
|
||||
const {name, data = {}} = item
|
||||
const {matter = {}, meta = {}} = data
|
||||
const title = matter.title || meta.title
|
||||
const defaultTitle = apStyleTitleCase(
|
||||
name.replace(/\/$/, '').split('/').pop()
|
||||
)
|
||||
const description = matter.description || meta.description
|
||||
const time = (
|
||||
meta.readingTime
|
||||
? Array.isArray(meta.readingTime)
|
||||
? meta.readingTime
|
||||
: [meta.readingTime, meta.readingTime]
|
||||
: []
|
||||
).map((d) => Math.ceil(d))
|
||||
let timeLabel
|
||||
|
||||
if (time.length > 1 && time[0] !== time[1]) {
|
||||
timeLabel = time[0] + '-' + time[1] + ' minutes'
|
||||
} else if (time[0]) {
|
||||
timeLabel = time[0] + ' minute' + (time[0] > 1 ? 's' : '')
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<h3>
|
||||
<a href={name}>{title || defaultTitle}</a>
|
||||
</h3>
|
||||
<div>
|
||||
{meta.descriptionHast ? (
|
||||
toH(createElement, meta.descriptionHast)
|
||||
) : description ? (
|
||||
<p>{description}</p>
|
||||
) : null}
|
||||
<span>
|
||||
<a href={name}>Read more »</a>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{display: 'flex', justifyContent: 'space-between'}}
|
||||
className="block"
|
||||
>
|
||||
<div>
|
||||
<small>By {meta.author}</small>
|
||||
<br />
|
||||
<small>Reading time: {timeLabel}</small>
|
||||
</div>
|
||||
<div style={{marginLeft: 'auto', textAlign: 'right'}}>
|
||||
<small>Published on {dateTimeFormat.format(meta.published)}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const BlogGroup = (props) => {
|
||||
const {items, className, sort = 'navSortSelf,meta.title', ...rest} = props
|
||||
const sorted = sortItems(items, sort)
|
||||
|
||||
return (
|
||||
<>
|
||||
{sorted.map((d) => (
|
||||
<BlogEntry key={d.name} {...rest} item={d} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
@ -19,13 +19,21 @@ export const NavGroup = (props) => {
|
||||
export const NavItem = (props) => {
|
||||
const {item, name: activeName, includeDescription, includePublished} = props
|
||||
const {name, children, data = {}} = item
|
||||
const {matter = {}, meta = {}, navLabel, navExcludeGroup, navSortItems} = data
|
||||
const {matter = {}, meta = {}, navExcludeGroup, navSortItems} = data
|
||||
const title = matter.title || meta.title
|
||||
const description = matter.description || meta.description
|
||||
const published = matter.published || meta.published
|
||||
const defaultTitle = apStyleTitleCase(
|
||||
name.replace(/\/$/, '').split('/').pop()
|
||||
)
|
||||
let description
|
||||
let published
|
||||
|
||||
if (includeDescription) {
|
||||
description = matter.description || meta.description
|
||||
}
|
||||
|
||||
if (includePublished && (matter.published || meta.published)) {
|
||||
published = dateTimeFormat.format(matter.published || meta.published)
|
||||
}
|
||||
|
||||
return (
|
||||
<li>
|
||||
@ -36,11 +44,8 @@ export const NavItem = (props) => {
|
||||
) : (
|
||||
defaultTitle
|
||||
)}
|
||||
{navLabel ? <sup>[{navLabel}]</sup> : null}
|
||||
{includeDescription && description ? ' — ' + description : null}
|
||||
{includePublished && published
|
||||
? ' — ' + dateTimeFormat.format(published)
|
||||
: null}
|
||||
{description ? ' — ' + description : null}
|
||||
{published ? ' — ' + published : null}
|
||||
{!navExcludeGroup && children.length > 0 ? (
|
||||
<NavGroup items={children} sort={navSortItems} name={activeName} />
|
||||
) : null}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {NavGroup} from '../_component/nav.server.js'
|
||||
import {BlogGroup} from '../_component/blog.server.js'
|
||||
export const navExcludeGroup = true
|
||||
export const navSortItems = 'navSortSelf,meta.published:desc'
|
||||
|
||||
@ -12,7 +12,7 @@ export const navSortItems = 'navSortSelf,meta.published:desc'
|
||||
|
||||
return (
|
||||
<nav>
|
||||
<NavGroup items={category.children} sort={navSortItems} includePublished />
|
||||
<BlogGroup items={category.children} sort={navSortItems} />
|
||||
</nav>
|
||||
)
|
||||
})()
|
||||
|
@ -1,4 +1,4 @@
|
||||
[MDX Blog](/blog)
|
||||
{/* To do: make the below a callout */}
|
||||
|
||||
> [ZEIT is now Vercel](https://rauchg.com/2020/vercel).
|
||||
> The below is kept as is for historical purposes.
|
||||
|
@ -1,3 +1,5 @@
|
||||
export const navExclude = true
|
||||
|
||||
# Migrating from v0 to v1
|
||||
|
||||
Unfortunately, we’ve had to introduce a few breaking changes, so we’ve written a
|
||||
|
@ -8,9 +8,12 @@ import {pipeToNodeWritable} from 'react-server-dom-webpack/writer'
|
||||
import pAll from 'p-all'
|
||||
import {globby} from 'globby'
|
||||
import {sitemap} from 'xast-util-sitemap'
|
||||
import {unified} from 'unified'
|
||||
import rehypeSanitize from 'rehype-sanitize'
|
||||
import {toXml} from 'xast-util-to-xml'
|
||||
import {Layout} from '../docs/_component/layout.server.js'
|
||||
import {config} from '../docs/_config.js'
|
||||
import {schema} from './schema-description.js'
|
||||
|
||||
main().catch((error) => {
|
||||
throw error
|
||||
@ -42,6 +45,13 @@ async function main() {
|
||||
|
||||
const {default: Content, ...data} = await import(url.href)
|
||||
|
||||
// Sanitize the HAST description:
|
||||
if (data.meta.descriptionHast) {
|
||||
data.meta.descriptionHast = unified()
|
||||
.use(rehypeSanitize, schema)
|
||||
.runSync(data.meta.descriptionHast)
|
||||
}
|
||||
|
||||
return {name, url, ghUrl, nljsonUrl, jsonUrl, data, Content}
|
||||
}),
|
||||
{concurrency: 6}
|
||||
|
@ -49,7 +49,10 @@ const options = {
|
||||
rehypePlugins: [
|
||||
unifiedInferRemoteMeta,
|
||||
unifiedInferGitMeta,
|
||||
[rehypeInferDescriptionMeta, {inferDescriptionHast: true}],
|
||||
[
|
||||
rehypeInferDescriptionMeta,
|
||||
{inferDescriptionHast: true, truncateSize: 280}
|
||||
],
|
||||
rehypeInferReadingTimeMeta,
|
||||
rehypeInferTitleMeta,
|
||||
[rehypeShiftHeading, {shift: 1}],
|
||||
|
@ -15,35 +15,10 @@ import rehypeStringify from 'rehype-stringify'
|
||||
import captureWebsite from 'capture-website'
|
||||
import chromium from 'chrome-aws-lambda'
|
||||
import {config} from '../docs/_config.js'
|
||||
import {schema} from './schema-description.js'
|
||||
|
||||
const dateTimeFormat = new Intl.DateTimeFormat('en')
|
||||
|
||||
const schema = {
|
||||
strip: ['script', 'style'],
|
||||
ancestors: {},
|
||||
protocols: {href: ['http', 'https']},
|
||||
tagNames: [
|
||||
'ul',
|
||||
'ol',
|
||||
'li',
|
||||
'pre',
|
||||
'code',
|
||||
'strong',
|
||||
'p',
|
||||
'b',
|
||||
'em',
|
||||
'i',
|
||||
'strike',
|
||||
's',
|
||||
'del',
|
||||
'a'
|
||||
],
|
||||
attributes: {
|
||||
a: ['href'],
|
||||
'*': []
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
throw error
|
||||
})
|
||||
|
25
website/schema-description.js
Normal file
25
website/schema-description.js
Normal file
@ -0,0 +1,25 @@
|
||||
export const schema = {
|
||||
strip: ['script', 'style'],
|
||||
ancestors: {},
|
||||
protocols: {href: ['http', 'https']},
|
||||
tagNames: [
|
||||
'ul',
|
||||
'ol',
|
||||
'li',
|
||||
'pre',
|
||||
'code',
|
||||
'strong',
|
||||
'p',
|
||||
'b',
|
||||
'em',
|
||||
'i',
|
||||
'strike',
|
||||
's',
|
||||
'del',
|
||||
'a'
|
||||
],
|
||||
attributes: {
|
||||
a: ['href'],
|
||||
'*': []
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user