use Next.js
This commit is contained in:
1 parent
cf94c66807
commit
20a00d5ab3
34 files changed
+7117
-1
No files matched your search
@@ -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)
|
||||
@@ -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()
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
Reference in new issue
Block a user