From a2cc749bd753080344e5a13d47d0621aa225f05b Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Sun, 18 Feb 2024 18:36:10 +0100 Subject: [PATCH] feat: convert Markdown files to HTML (#1417) --- package-lock.json | 33 +++++++++++++++++++++++++- package.json | 4 +++- src/lib/markdown.ts | 9 +++++++ src/lib/parser/DeckParser.ts | 3 ++- src/lib/parser/getHTMLContents.test.ts | 11 +++++++++ src/lib/parser/getHTMLContents.ts | 18 ++++++++++++++ 6 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 src/lib/markdown.ts create mode 100644 src/lib/parser/getHTMLContents.test.ts create mode 100644 src/lib/parser/getHTMLContents.ts diff --git a/package-lock.json b/package-lock.json index fdd74ffdd..00472866e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,8 @@ "multer": "^1.4.4-lts.1", "pg": "^8.11.3", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "showdown": "^2.1.0" }, "devDependencies": { "@types/base-64": "^1.0.0", @@ -58,6 +59,7 @@ "@types/node-fetch": "^2.6.4", "@types/react": "^18.2.43", "@types/react-dom": "18.2.18", + "@types/showdown": "^2.0.6", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.15.0", "eslint": "^8.53.0", @@ -3580,6 +3582,12 @@ "@types/node": "*" } }, + "node_modules/@types/showdown": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.6.tgz", + "integrity": "sha512-pTvD/0CIeqe4x23+YJWlX2gArHa8G0J0Oh6GKaVXV7TAeickpkkZiNOgFcFcmLQ5lB/K0qBJL1FtRYltBfbGCQ==", + "dev": true + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -11375,6 +11383,29 @@ "node": ">=8" } }, + "node_modules/showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "dependencies": { + "commander": "^9.0.0" + }, + "bin": { + "showdown": "bin/showdown.js" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/tiviesantos" + } + }, + "node_modules/showdown/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", diff --git a/package.json b/package.json index 6d65c3afd..371532910 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,8 @@ "multer": "^1.4.4-lts.1", "pg": "^8.11.3", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "showdown": "^2.1.0" }, "devDependencies": { "@types/base-64": "^1.0.0", @@ -79,6 +80,7 @@ "@types/node-fetch": "^2.6.4", "@types/react": "^18.2.43", "@types/react-dom": "18.2.18", + "@types/showdown": "^2.0.6", "@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/parser": "^6.15.0", "eslint": "^8.53.0", diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts new file mode 100644 index 000000000..39e349da9 --- /dev/null +++ b/src/lib/markdown.ts @@ -0,0 +1,9 @@ +import showdown from 'showdown'; + +export const markdownToHTML = (html: string) => { + const converter = new showdown.Converter({ + noHeaderId: true, + }); + + return converter.makeHtml(html); +}; diff --git a/src/lib/parser/DeckParser.ts b/src/lib/parser/DeckParser.ts index 52e013783..f6d4ac6e7 100644 --- a/src/lib/parser/DeckParser.ts +++ b/src/lib/parser/DeckParser.ts @@ -22,6 +22,7 @@ import getYouTubeID from './helpers/getYouTubeID'; import { isFileNameEqual } from '../storage/types'; import { isImageFileEmbedable } from '../storage/checks'; import getDeckFilename from '../anki/getDeckFilename'; +import { getHTMLContents } from './getHTMLContents'; export class DeckParser { globalTags: cheerio.Cheerio | null; @@ -45,8 +46,8 @@ export class DeckParser { this.globalTags = null; const firstFile = this.files.find((file) => isFileNameEqual(file, name)); + const contents = getHTMLContents(firstFile); - const contents = firstFile?.contents; if (contents) { this.payload = this.handleHTML( name, diff --git a/src/lib/parser/getHTMLContents.test.ts b/src/lib/parser/getHTMLContents.test.ts new file mode 100644 index 000000000..88fac5871 --- /dev/null +++ b/src/lib/parser/getHTMLContents.test.ts @@ -0,0 +1,11 @@ +import { describe } from 'node:test'; +import { getHTMLContents } from './getHTMLContents'; + +describe("getHTMLContents", () => { + test("returns html contents", () => { + expect(getHTMLContents({contents: "

html

", name: "index.html"})).toBe("

html

") + }) + test("returns html for markdown", () => { + expect(getHTMLContents({contents: "# md", name: "README.md"})).toBe("

md

") + }) +}) diff --git a/src/lib/parser/getHTMLContents.ts b/src/lib/parser/getHTMLContents.ts new file mode 100644 index 000000000..912da7caf --- /dev/null +++ b/src/lib/parser/getHTMLContents.ts @@ -0,0 +1,18 @@ +import { isHTMLFile, isMarkdownFile } from '../storage/checks'; +import { markdownToHTML } from '../markdown'; +import { File } from '../anki/zip'; + +export function getHTMLContents(file: File | undefined) { + const contents = file?.contents; + if (!file || !contents) { + return undefined; + } + + if (isHTMLFile(file.name)) { + return file.contents; + } + + if (isMarkdownFile(file.name)) { + return markdownToHTML(contents.toString()); + } +}