From 3a879edc235a58a23372a9be7e16dee84a1dae26 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Mon, 4 Apr 2022 18:13:18 -0700 Subject: [PATCH] client/server: move markdown rendering from client/ entirely to server/ --- client/components/preview/index.tsx | 5 +- client/lib/render-markdown.tsx | 152 --------------------- client/pages/api/markdown/[id].ts | 57 -------- client/pages/api/render-markdown.ts | 42 ------ server/src/lib/get-html-from-drift-file.ts | 41 ++++++ server/src/routes/files.ts | 25 ++++ server/src/routes/posts.ts | 33 +---- 7 files changed, 71 insertions(+), 284 deletions(-) delete mode 100644 client/lib/render-markdown.tsx delete mode 100644 client/pages/api/markdown/[id].ts delete mode 100644 client/pages/api/render-markdown.ts create mode 100644 server/src/lib/get-html-from-drift-file.ts diff --git a/client/components/preview/index.tsx b/client/components/preview/index.tsx index 3180a53a..4e02fb4e 100644 --- a/client/components/preview/index.tsx +++ b/client/components/preview/index.tsx @@ -1,3 +1,4 @@ +import Cookies from "js-cookie" import { memo, useEffect, useState } from "react" import styles from './preview.module.css' @@ -24,16 +25,18 @@ const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => { setIsLoading(false) } } else if (content) { - const resp = await fetch(`/api/render-markdown`, { + const resp = await fetch("/server-api/files/html", { method: "POST", headers: { "Content-Type": "application/json", + "Authorization": `Bearer ${Cookies.get("drift-token") || ""}`, }, body: JSON.stringify({ title, content, }), }) + if (resp.ok) { const res = await resp.text() setPreview(res) diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx deleted file mode 100644 index 04bfcb86..00000000 --- a/client/lib/render-markdown.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { marked } from 'marked' -import Highlight, { defaultProps, Language, } from 'prism-react-renderer' -import { renderToStaticMarkup } from 'react-dom/server' - -// // image sizes. DDoS Safe? -// const imageSizeLink = /^!?\[((?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?)\]\(\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?(?:\s+=(?:[\w%]+)?x(?:[\w%]+)?)?)(?:\s+("(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)))?\s*\)/; -// //@ts-ignore -// Lexer.rules.inline.normal.link = imageSizeLink; -// //@ts-ignore -// Lexer.rules.inline.gfm.link = imageSizeLink; -// //@ts-ignore -// Lexer.rules.inline.breaks.link = imageSizeLink; - -//@ts-ignore -delete defaultProps.theme -// import linkStyles from '../components/link/link.module.css' - -const renderer = new marked.Renderer() - -renderer.heading = (text, level, _, slugger) => { - const id = slugger.slug(text) - const Component = `h${level}` - - return renderToStaticMarkup( - //@ts-ignore - - - - - ) -} - -// renderer.link = (href, _, text) => { -// const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') -// if (isHrefLocal) { -// return renderToStaticMarkup( -// -// {text} -// -// ) -// } - -// // dirty hack -// // if text contains elements, render as html -// return -// } - - -renderer.image = function (href, _, text) { - return `${text}` -} - -renderer.checkbox = () => '' -renderer.listitem = (text, task, checked) => { - if (task) { - return `
  • ${text}
  • ` - } - - return `
  • ${text}
  • ` -} - -renderer.code = (code: string, language: string) => { - return renderToStaticMarkup( -
    -            {/* {title && {title} } */}
    -            {/* {language && title &&  {language} } */}
    -            
    -        
    - ) -} - -marked.setOptions({ - gfm: true, - breaks: true, - headerIds: true, - renderer, -}) - -const markdown = (markdown: string) => marked(markdown) - -export default markdown - -const Code = ({ code, language, highlight, title, ...props }: { - code: string, - language: string, - highlight?: string, - title?: string, -}) => { - if (!language) - return ( - <> - - - ) - - const highlightedLines = highlight - //@ts-ignore - ? highlight.split(',').reduce((lines, h) => { - if (h.includes('-')) { - // Expand ranges like 3-5 into [3,4,5] - const [start, end] = h.split('-').map(Number) - const x = Array(end - start + 1) - .fill(undefined) - .map((_, i) => i + start) - return [...lines, ...x] - } - - return [...lines, Number(h)] - }, []) - : '' - - // https://mdxjs.com/guides/syntax-harkedighlighting#all-together - return ( - <> - - {({ className, style, tokens, getLineProps, getTokenProps }) => ( - - { - tokens.map((line, i) => ( -
    - { - line.map((token, key) => ( - - )) - } -
    - ))} -
    - )} -
    - - ) -} diff --git a/client/pages/api/markdown/[id].ts b/client/pages/api/markdown/[id].ts deleted file mode 100644 index 79f186ec..00000000 --- a/client/pages/api/markdown/[id].ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { NextApiHandler } from "next" - -import markdown from "@lib/render-markdown" - -const renderMarkdown: NextApiHandler = async (req, res) => { - const { id } = req.query - const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { - headers: { - Accept: "text/plain", - "x-secret-key": process.env.SECRET_KEY || "", - Authorization: `Bearer ${req.cookies["drift-token"]}` - } - }) - if (file.status !== 200) { - return res.status(404).json({ error: "File not found" }) - } - - const json = await file.json() - const { content, title } = json - const renderAsMarkdown = [ - "markdown", - "md", - "mdown", - "mkdn", - "mkd", - "mdwn", - "mdtxt", - "mdtext", - "text", - "" - ] - const fileType = () => { - const pathParts = title.split(".") - const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" - return language - } - const type = fileType() - let contentToRender: string = "\n" + content - - if (!renderAsMarkdown.includes(type)) { - contentToRender = `~~~${type} -${content} -~~~` - } - - if (typeof contentToRender !== "string") { - res.status(400).send("content must be a string") - return - } - - res.setHeader("Content-Type", "text/plain") - res.setHeader("Cache-Control", "public, max-age=4800") - res.status(200).write(markdown(contentToRender)) - res.end() -} - -export default renderMarkdown diff --git a/client/pages/api/render-markdown.ts b/client/pages/api/render-markdown.ts deleted file mode 100644 index 74aaee46..00000000 --- a/client/pages/api/render-markdown.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { NextApiHandler } from "next" - -import markdown from "@lib/render-markdown" - -const renderMarkdown: NextApiHandler = async (req, res) => { - const { content, title } = req.body - const renderAsMarkdown = [ - "markdown", - "md", - "mdown", - "mkdn", - "mkd", - "mdwn", - "mdtxt", - "mdtext", - "text", - "" - ] - const fileType = () => { - const pathParts = title.split(".") - const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" - return language - } - const type = fileType() - let contentToRender: string = content || "" - if (!renderAsMarkdown.includes(type)) { - contentToRender = `~~~${type} -${content} -~~~` - } else { - contentToRender = "\n" + content - } - - if (typeof contentToRender !== "string") { - res.status(400).send("content must be a string") - return - } - res.status(200).write(markdown(contentToRender)) - res.end() -} - -export default renderMarkdown diff --git a/server/src/lib/get-html-from-drift-file.ts b/server/src/lib/get-html-from-drift-file.ts new file mode 100644 index 00000000..4e796021 --- /dev/null +++ b/server/src/lib/get-html-from-drift-file.ts @@ -0,0 +1,41 @@ +import markdown from "./render-markdown" +import { File } from "@lib/models/File" + +/** + * returns rendered HTML from a Drift file + */ +function getHtmlFromFile({ content, title }: Pick) { + const renderAsMarkdown = [ + "markdown", + "md", + "mdown", + "mkdn", + "mkd", + "mdwn", + "mdtxt", + "mdtext", + "text", + "" + ] + const fileType = () => { + const pathParts = title.split(".") + const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" + return language + } + const type = fileType() + let contentToRender: string = content || "" + + if (!renderAsMarkdown.includes(type)) { + contentToRender = `~~~${type} +${content} +~~~` + } else { + contentToRender = "\n" + content + } + console.log(contentToRender.slice(0, 50)) + const html = markdown(contentToRender) + return html +} + + +export default getHtmlFromFile \ No newline at end of file diff --git a/server/src/routes/files.ts b/server/src/routes/files.ts index fe227bed..a4c05c61 100644 --- a/server/src/routes/files.ts +++ b/server/src/routes/files.ts @@ -2,9 +2,34 @@ import { celebrate, Joi } from "celebrate" import { Router } from "express" import { File } from "@lib/models/File" import secretKey from "@lib/middleware/secret-key" +import jwt from "@lib/middleware/jwt" +import getHtmlFromFile from "@lib/get-html-from-drift-file" export const files = Router() +files.post( + "/html", + jwt, + // celebrate({ + // body: Joi.object().keys({ + // content: Joi.string().required().allow(""), + // title: Joi.string().required().allow(""), + // }) + // }), + async (req, res, next) => { + const { content, title } = req.body + const renderedHtml = getHtmlFromFile({ + content, + title + }) + + res.setHeader("Content-Type", "text/plain") + // res.setHeader("Cache-Control", "public, max-age=4800") + res.status(200).write(renderedHtml) + res.end() + } +) + files.get( "/raw/:id", celebrate({ diff --git a/server/src/routes/posts.ts b/server/src/routes/posts.ts index cf926982..7795a1e1 100644 --- a/server/src/routes/posts.ts +++ b/server/src/routes/posts.ts @@ -6,9 +6,9 @@ import jwt, { UserJwtRequest } from "@lib/middleware/jwt" import * as crypto from "crypto" import { User } from "@lib/models/User" import secretKey from "@lib/middleware/secret-key" -import markdown from "@lib/render-markdown" import { Op } from "sequelize" import { PostAuthor } from "@lib/models/PostAuthor" +import getHtmlFromFile from "@lib/get-html-from-drift-file" export const posts = Router() @@ -358,34 +358,3 @@ posts.delete("/:id", jwt, async (req: UserJwtRequest, res, next) => { } }) -function getHtmlFromFile(file: any) { - const renderAsMarkdown = [ - "markdown", - "md", - "mdown", - "mkdn", - "mkd", - "mdwn", - "mdtxt", - "mdtext", - "text", - "" - ] - const fileType = () => { - const pathParts = file.title.split(".") - const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" - return language - } - const type = fileType() - let contentToRender: string = file.content || "" - - if (!renderAsMarkdown.includes(type)) { - contentToRender = `~~~${type} -${file.content} -~~~` - } else { - contentToRender = "\n" + file.content - } - const html = markdown(contentToRender) - return html -}