use Next.js

This commit is contained in:
polv committed 2020-12-17 23:15:28 +07:00
1 parent cf94c66807
commit 20a00d5ab3
34 files changed
+7117 -1

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
import fs from 'fs'
import path from 'path'
import dotenv from 'dotenv'
import { THEME_FILENAME } from '../server/theme'
dotenv.config({ path: '../../.env' })
dotenv.config()
export const CONTENT_PATH = path.resolve(
process.env.CONTENT_PATH ||
(fs.existsSync('../../_post') ? '../..' : 'content')
)
export const srcPath = (
root: '_post' | '_media' | typeof THEME_FILENAME,
...ps: string[]
): string => path.join(CONTENT_PATH, root, ...ps)
+153
View File
@@ -0,0 +1,153 @@
import cheerio from 'cheerio'
import crypto from 'crypto'
import ejs from 'ejs'
import scopeCss from '@patarapolw/scope-css'
import showdown from 'showdown'
export class MakeHtml {
$ = cheerio.load('')
mdConverter = new showdown.Converter({
noHeaderId: true,
parseImgDimensions: true,
simplifiedAutoLink: true,
// literalMidWordUnderscores: true,
strikethrough: true,
tables: true,
tasklists: true,
// simpleLineBreaks: true,
// openLinksInNewWindow: true,
backslashEscapesHTMLTags: true,
emoji: true,
underline: true,
disableForced4SpacesIndentedSublists: true,
metadata: true
})
constructor () {
this.mdConverter.addExtension(
{
type: 'lang',
regex: /\\\n/g,
replace: '<br />'
},
'commonmark-simple-linebreak'
)
/**
* ! For some reasons default underscore italic fails to render...
*/
this.mdConverter.addExtension(
{
type: 'lang',
regex: /([^\\])_{1}(.*?[^\\])_{1}/g,
replace: '$1<i>$2</i>'
},
'underscore-italic'
)
/**
* ! This is the only way to preserve syntax highlighting, while output multiline strings correctly
*/
this.mdConverter.addExtension(
{
type: 'lang',
regex: /(^|\n)```html parsed([^]+)\n```\n/g,
replace: (_: string, p1: string, p2: string) => {
return (
p1 +
ejs.render(p2, {
rmWhitespace: true,
beautify: false
})
)
}
},
'html-parsed'
)
}
async parse (
filename: string
): Promise<{
html: string
text: string
}> {
const id =
'md-' +
crypto
.createHash('sha256')
.update(filename)
.digest()
.toString('hex')
.substr(0, 8)
const $div = this.$(`<div id="${id}">`)
$div.html(await this.getHTML(filename))
$div.find('style').each((_, el) => {
const $el = this.$(el)
const html = $el.html() || ''
if (html.trim()) {
$el.html(scopeCss(html, `#${id}`))
}
})
$div.find('a').each((_, el) => {
const $el = this.$(el)
const href = $el.attr('href') || ''
if (/^https?:\/\//.test(href)) {
$el.attr({
rel: 'noopener',
target: '_blank'
})
}
})
return {
html: this.$('<div>').append($div).html() || '',
text: $div.text().trim()
}
}
private async getHTML (filename: string) {
return this.mdConverter.makeHtml(
await ejs.renderFile(filename, {
youtube: (id: string) =>
ejs.render(
/* html */ `
<div style="position:relative;padding-top:56.25%;">
<iframe src="https://www.youtube.com/embed/${id}"
frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
style="position:absolute;top:0;left:0;width:100%;height:100%"></iframe>
</div>`,
{ rmWhitespace: true, beautify: false }
),
xCard: (opts: {
href: string
image?: string
title: string
description?: string
}) =>
this.$('<div>')
.append(
this.$('<x-card>')
.attr(opts)
.append(
this.$('<a>')
.attr({
href: opts.href,
rel: 'noopener',
target: '_blank'
})
.text(opts.title)
)
)
.html()
})
)
}
}
+91
View File
@@ -0,0 +1,91 @@
import { IPost, db, dbPost, initDatabase, zMatter, zPost } from '../server/db'
import { buildPath, dstMediaPath } from '../server/dir'
import { MakeHtml } from './make-html'
import { THEME_FILENAME } from '../server/theme'
import fg from 'fast-glob'
import fs from 'fs-extra'
import jieba from 'nodejieba'
import lunr from 'lunr'
import rimraf from 'rimraf'
import { srcPath } from './dir'
import yaml from 'js-yaml'
async function main () {
rimraf.sync(dstMediaPath())
rimraf.sync(buildPath())
await fs.copy(srcPath('_media'), dstMediaPath()).catch((e) => {
// eslint-disable-next-line no-console
console.error(e)
})
await fs.ensureDir(buildPath())
await fs.writeFile(
buildPath('theme.json'),
JSON.stringify(
yaml.safeLoad(await fs.readFile(srcPath(THEME_FILENAME), 'utf8'))
)
)
const files = await fg('**/*.md', {
cwd: srcPath('_post')
})
const makeHtml = new MakeHtml()
const rawJson: IPost[] = []
await initDatabase()
await Promise.all(
files.map(async (f) => {
const header = zMatter.parse(
yaml.safeLoad(
fs.readFileSync(srcPath('_post', f), 'utf-8').split(/\n---\n/)[0]
)
)
const { html, text } = await makeHtml.parse(srcPath('_post', f))
const p: IPost = {
...header,
date: header.date ? header.date.getTime() : undefined,
createdAt: header.createdAt ? header.createdAt.getTime() : undefined,
updatedAt: header.updatedAt ? header.updatedAt.getTime() : undefined,
path: f.replace(/\.mdx?$/, ''),
excerptHtml: html.split(/<!-- excerpt(?:_separator)? -->/)[0],
contentHtml: html,
content: text
}
dbPost.insert(zPost.parse(p))
rawJson.push({
...p,
content: jieba.cutForSearch(p.content).join(' ')
})
})
)
db.save()
fs.writeFileSync(
buildPath('idx.json'),
JSON.stringify(
lunr(function () {
this.ref('path')
this.field('title', { boost: 5 })
this.field('tag', { boost: 5 })
this.field('category', { boost: 5 })
this.field('content')
rawJson.map((doc) => {
return this.add(doc)
})
})
)
)
}
if (require.main === module) {
main()
}