feat: import posts from file
This commit is contained in:
parent
f1d1f0ebcf
commit
9dd09b2cbb
|
|
@ -58,6 +58,7 @@
|
|||
"form-data": "^4.0.0",
|
||||
"idb-keyval": "^6.2.0",
|
||||
"ioredis": "^5.2.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"mailgun.js": "^5.2.2",
|
||||
"mdast-util-to-string": "^3.1.0",
|
||||
"nanoid": "^3.3.3",
|
||||
|
|
@ -77,9 +78,11 @@
|
|||
"rehype-raw": "^6.1.1",
|
||||
"rehype-sanitize": "^5.0.1",
|
||||
"rehype-stringify": "^9.0.3",
|
||||
"remark-frontmatter": "^4.0.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-parse": "^10.0.1",
|
||||
"remark-rehype": "^10.1.0",
|
||||
"remark-stringify": "^10.0.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"ua-parser-js": "^1.0.2",
|
||||
"unidata.js": "^0.6.21",
|
||||
|
|
@ -104,6 +107,7 @@
|
|||
"@types/async-busboy": "^1.1.1",
|
||||
"@types/busboy": "^1.5.0",
|
||||
"@types/cookie": "^0.5.1",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/mjml": "^4.7.0",
|
||||
"@types/node": "^17.0.31",
|
||||
"@types/pica": "^9.0.0",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ specifiers:
|
|||
'@types/async-busboy': ^1.1.1
|
||||
'@types/busboy': ^1.5.0
|
||||
'@types/cookie': ^0.5.1
|
||||
'@types/js-yaml': ^4.0.5
|
||||
'@types/mjml': ^4.7.0
|
||||
'@types/node': ^17.0.31
|
||||
'@types/pica': ^9.0.0
|
||||
|
|
@ -70,6 +71,7 @@ specifiers:
|
|||
husky: ^8.0.1
|
||||
idb-keyval: ^6.2.0
|
||||
ioredis: ^5.2.2
|
||||
js-yaml: ^4.1.0
|
||||
lint-staged: ^13.0.3
|
||||
mailgun.js: ^5.2.2
|
||||
mdast-util-to-string: ^3.1.0
|
||||
|
|
@ -93,9 +95,11 @@ specifiers:
|
|||
rehype-raw: ^6.1.1
|
||||
rehype-sanitize: ^5.0.1
|
||||
rehype-stringify: ^9.0.3
|
||||
remark-frontmatter: ^4.0.1
|
||||
remark-gfm: ^3.0.1
|
||||
remark-parse: ^10.0.1
|
||||
remark-rehype: ^10.1.0
|
||||
remark-stringify: ^10.0.2
|
||||
sortablejs: ^1.15.0
|
||||
tailwindcss: ^3.0.24
|
||||
typescript: ^4.5.5
|
||||
|
|
@ -153,6 +157,7 @@ dependencies:
|
|||
form-data: 4.0.0
|
||||
idb-keyval: 6.2.0
|
||||
ioredis: 5.2.2
|
||||
js-yaml: 4.1.0
|
||||
mailgun.js: 5.2.2
|
||||
mdast-util-to-string: 3.1.0
|
||||
nanoid: 3.3.4
|
||||
|
|
@ -172,9 +177,11 @@ dependencies:
|
|||
rehype-raw: 6.1.1
|
||||
rehype-sanitize: 5.0.1
|
||||
rehype-stringify: 9.0.3
|
||||
remark-frontmatter: 4.0.1
|
||||
remark-gfm: 3.0.1
|
||||
remark-parse: 10.0.1
|
||||
remark-rehype: 10.1.0
|
||||
remark-stringify: 10.0.2
|
||||
sortablejs: 1.15.0
|
||||
ua-parser-js: 1.0.2
|
||||
unidata.js: 0.6.21
|
||||
|
|
@ -199,6 +206,7 @@ devDependencies:
|
|||
'@types/async-busboy': 1.1.1
|
||||
'@types/busboy': 1.5.0
|
||||
'@types/cookie': 0.5.1
|
||||
'@types/js-yaml': 4.0.5
|
||||
'@types/mjml': 4.7.0
|
||||
'@types/node': 17.0.45
|
||||
'@types/pica': 9.0.1
|
||||
|
|
@ -2078,6 +2086,10 @@ packages:
|
|||
'@types/unist': 2.0.6
|
||||
dev: false
|
||||
|
||||
/@types/js-yaml/4.0.5:
|
||||
resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
|
||||
dev: true
|
||||
|
||||
/@types/json-schema/7.0.11:
|
||||
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
||||
dev: true
|
||||
|
|
@ -2993,7 +3005,6 @@ packages:
|
|||
|
||||
/argparse/2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
dev: true
|
||||
|
||||
/aria-hidden/1.1.3:
|
||||
resolution: {integrity: sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==}
|
||||
|
|
@ -5080,6 +5091,12 @@ packages:
|
|||
reusify: 1.0.4
|
||||
dev: true
|
||||
|
||||
/fault/2.0.1:
|
||||
resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
|
||||
dependencies:
|
||||
format: 0.2.2
|
||||
dev: false
|
||||
|
||||
/fetch-blob/2.1.2:
|
||||
resolution: {integrity: sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==}
|
||||
engines: {node: ^10.17.0 || >=12.3.0}
|
||||
|
|
@ -5192,6 +5209,11 @@ packages:
|
|||
mime-types: 2.1.35
|
||||
dev: false
|
||||
|
||||
/format/0.2.2:
|
||||
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
|
||||
engines: {node: '>=0.4.x'}
|
||||
dev: false
|
||||
|
||||
/fraction.js/4.2.0:
|
||||
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
|
||||
dev: true
|
||||
|
|
@ -6195,7 +6217,6 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
dev: true
|
||||
|
||||
/jsesc/2.5.2:
|
||||
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
|
||||
|
|
@ -6594,6 +6615,12 @@ packages:
|
|||
- supports-color
|
||||
dev: false
|
||||
|
||||
/mdast-util-frontmatter/1.0.0:
|
||||
resolution: {integrity: sha512-7itKvp0arEVNpCktOET/eLFAYaZ+0cNjVtFtIPxgQ5tV+3i+D4SDDTjTzPWl44LT59PC+xdx+glNTawBdF98Mw==}
|
||||
dependencies:
|
||||
micromark-extension-frontmatter: 1.0.0
|
||||
dev: false
|
||||
|
||||
/mdast-util-gfm-autolink-literal/1.0.2:
|
||||
resolution: {integrity: sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==}
|
||||
dependencies:
|
||||
|
|
@ -6743,6 +6770,14 @@ packages:
|
|||
uvu: 0.5.4
|
||||
dev: false
|
||||
|
||||
/micromark-extension-frontmatter/1.0.0:
|
||||
resolution: {integrity: sha512-EXjmRnupoX6yYuUJSQhrQ9ggK0iQtQlpi6xeJzVD5xscyAI+giqco5fdymayZhJMbIFecjnE2yz85S9NzIgQpg==}
|
||||
dependencies:
|
||||
fault: 2.0.1
|
||||
micromark-util-character: 1.1.0
|
||||
micromark-util-symbol: 1.0.1
|
||||
dev: false
|
||||
|
||||
/micromark-extension-gfm-autolink-literal/1.0.3:
|
||||
resolution: {integrity: sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==}
|
||||
dependencies:
|
||||
|
|
@ -8222,6 +8257,15 @@ packages:
|
|||
unified: 10.1.2
|
||||
dev: false
|
||||
|
||||
/remark-frontmatter/4.0.1:
|
||||
resolution: {integrity: sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
mdast-util-frontmatter: 1.0.0
|
||||
micromark-extension-frontmatter: 1.0.0
|
||||
unified: 10.1.2
|
||||
dev: false
|
||||
|
||||
/remark-gfm/3.0.1:
|
||||
resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==}
|
||||
dependencies:
|
||||
|
|
@ -8252,6 +8296,14 @@ packages:
|
|||
unified: 10.1.2
|
||||
dev: false
|
||||
|
||||
/remark-stringify/10.0.2:
|
||||
resolution: {integrity: sha512-6wV3pvbPvHkbNnWB0wdDvVFHOe1hBRAx1Q/5g/EpH4RppAII6J8Gnwe7VbHuXaoKIF6LAg6ExTel/+kNqSQ7lw==}
|
||||
dependencies:
|
||||
'@types/mdast': 3.0.10
|
||||
mdast-util-to-markdown: 1.3.0
|
||||
unified: 10.1.2
|
||||
dev: false
|
||||
|
||||
/remove-accents/0.4.2:
|
||||
resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==}
|
||||
dev: false
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import { delStorage, getStorage, setStorage } from "~/lib/storage"
|
|||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import { Button } from "../ui/Button"
|
||||
import { UniLink } from "../ui/UniLink"
|
||||
import { nanoid } from "nanoid"
|
||||
import { renderPageContent } from "~/markdown"
|
||||
|
||||
export const PagesManager: React.FC<{
|
||||
isPost: boolean
|
||||
|
|
@ -242,30 +244,90 @@ export const PagesManager: React.FC<{
|
|||
<header className="mb-8">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-2xl font-bold">{title}</h2>
|
||||
<Button
|
||||
className={clsx(`space-x-2 inline-flex`)}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/dashboard/${subdomain}/editor?type=${
|
||||
isPost ? "post" : "page"
|
||||
}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
<div className="flex justify-center items-center">
|
||||
<Button
|
||||
className={clsx(`space-x-2 inline-flex mr-4`)}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/dashboard/${subdomain}/editor?type=${
|
||||
isPost ? "post" : "page"
|
||||
}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<path d="M12 20h9"></path>
|
||||
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
|
||||
</svg>
|
||||
<span>New {isPost ? "Post" : "Page"}</span>
|
||||
</Button>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M12 20h9"></path>
|
||||
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
|
||||
</svg>
|
||||
<span>New {isPost ? "Post" : "Page"}</span>
|
||||
</Button>
|
||||
<Button
|
||||
className={clsx(`space-x-2 inline-flex`)}
|
||||
onClick={() => {
|
||||
const input = document.createElement("input")
|
||||
input.type = "file"
|
||||
input.accept = "text/markdown"
|
||||
input.addEventListener("change", async (e: any) => {
|
||||
const file = e.target?.files?.[0]
|
||||
const reader = new FileReader()
|
||||
reader.readAsText(file, "UTF-8")
|
||||
reader.onload = async (evt) => {
|
||||
if (evt.target?.result) {
|
||||
const pageContent = await renderPageContent(
|
||||
evt.target.result as string,
|
||||
)
|
||||
|
||||
const id = nanoid()
|
||||
const key = `draft-${subdomain}-local-${id}`
|
||||
const tags =
|
||||
pageContent.frontMatter.tags ||
|
||||
pageContent.frontMatter.categories
|
||||
setStorage(key, {
|
||||
date: +new Date(),
|
||||
values: {
|
||||
content: evt.target.result,
|
||||
published: false,
|
||||
publishedAt: (
|
||||
pageContent.frontMatter.date || new Date()
|
||||
).toISOString(),
|
||||
slug:
|
||||
pageContent.frontMatter.permalink ||
|
||||
file.name.split(".").slice(0, -1).join("."),
|
||||
tags: tags?.join?.(", ") || tags,
|
||||
title: pageContent.frontMatter.title,
|
||||
},
|
||||
isPost: isPost,
|
||||
})
|
||||
queryClient.invalidateQueries([
|
||||
"getPagesBySite",
|
||||
subdomain,
|
||||
])
|
||||
router.push(
|
||||
`/dashboard/${subdomain}/editor?id=local-${id}&type=${
|
||||
isPost ? "post" : "page"
|
||||
}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
reader.onerror = (evt) => {
|
||||
toast.error("Error reading file")
|
||||
}
|
||||
})
|
||||
input.click()
|
||||
}}
|
||||
>
|
||||
<span className="i-bxs:duplicate inline-block"></span>
|
||||
<span>Import</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm text-zinc-500 leading-relaxed">
|
||||
{description}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { unified } from "unified"
|
||||
import remarkParse from "remark-parse"
|
||||
import remarkRehype from "remark-rehype"
|
||||
import remarkFrontmatter from "remark-frontmatter"
|
||||
import rehypeStringify from "rehype-stringify"
|
||||
import remarkGfm from "remark-gfm"
|
||||
import rehypeSanitize, { defaultSchema } from "rehype-sanitize"
|
||||
|
|
@ -13,15 +14,18 @@ import { rehypeTable } from "./rehype-table"
|
|||
import { allowedBlockquoteAttrs, remarkCallout } from "./remark-callout"
|
||||
import { rehypeExternalLink } from "./rehyper-external-link"
|
||||
import { rehypeWrapCode } from "./rehype-wrap-code"
|
||||
import jsYaml from "js-yaml"
|
||||
|
||||
export type MarkdownEnv = {
|
||||
excerpt: string
|
||||
frontMatter: Record<string, any>
|
||||
__internal: Record<string, any>
|
||||
}
|
||||
|
||||
export type Rendered = {
|
||||
contentHTML: string
|
||||
excerpt: string
|
||||
frontMatter: Record<string, any>
|
||||
}
|
||||
|
||||
refractor.alias("html", ["svelte", "vue"])
|
||||
|
|
@ -29,10 +33,23 @@ refractor.alias("html", ["svelte", "vue"])
|
|||
const rehypePrism = rehypePrismGenerator(refractor)
|
||||
|
||||
export const renderPageContent = async (content: string): Promise<Rendered> => {
|
||||
const env: MarkdownEnv = { excerpt: "", __internal: {} }
|
||||
const env: MarkdownEnv = { excerpt: "", __internal: {}, frontMatter: {} }
|
||||
|
||||
const result = await unified()
|
||||
.use(remarkParse)
|
||||
.use(rehypeStringify)
|
||||
.use(remarkFrontmatter, ["yaml"])
|
||||
.use(() => (tree) => {
|
||||
const yaml = tree.children.find((node) => node.type === "yaml")
|
||||
try {
|
||||
env.frontMatter = jsYaml.load((yaml as any)?.value) as Record<
|
||||
string,
|
||||
any
|
||||
>
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
})
|
||||
.use(remarkGfm, {})
|
||||
.use(remarkExcerpt, { env })
|
||||
.use(remarkCallout)
|
||||
|
|
@ -71,12 +88,11 @@ export const renderPageContent = async (content: string): Promise<Rendered> => {
|
|||
ignoreMissing: true,
|
||||
})
|
||||
.use(rehypeTable)
|
||||
.use(rehypeStringify)
|
||||
.use(rehypeExternalLink)
|
||||
.use(rehypeWrapCode)
|
||||
.process(content)
|
||||
|
||||
const contentHTML = result.toString()
|
||||
|
||||
return { contentHTML, excerpt: env.excerpt }
|
||||
return { contentHTML, excerpt: env.excerpt, frontMatter: env.frontMatter }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,11 +43,6 @@ export default function SubdomainEditor() {
|
|||
if (!pageId) {
|
||||
const key = `draft-${subdomain}-local-${nanoid()}`
|
||||
setDraftKey(key)
|
||||
setStorage(key, {
|
||||
date: +new Date(),
|
||||
isPost: isPost,
|
||||
values,
|
||||
})
|
||||
queryClient.invalidateQueries(["getPagesBySite", subdomain])
|
||||
} else {
|
||||
setDraftKey(`draft-${subdomain}-${pageId}`)
|
||||
|
|
@ -184,6 +179,7 @@ export default function SubdomainEditor() {
|
|||
})
|
||||
|
||||
useEffect(() => {
|
||||
console.log("!page.data", page.data)
|
||||
if (!page.data || !draftKey) return
|
||||
|
||||
const local = getStorage(draftKey)
|
||||
|
|
|
|||
Loading…
Reference in New Issue