Skip to content

Commit

Permalink
[#9] Add markdown rendering logic
Browse files Browse the repository at this point in the history
  • Loading branch information
3jins committed Aug 1, 2021
1 parent d4466ee commit 65e644e
Show file tree
Hide file tree
Showing 14 changed files with 616 additions and 45 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
# testing
**/.nyc_output
**/coverage
/backend/test/data/rendered

# local
/backend/appData
Expand Down
110 changes: 110 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@
"config": "^3.3.2",
"file-type": "^15.0.1",
"formidable": "^1.2.2",
"highlight.js": "^11.1.0",
"katex": "^0.13.13",
"koa": "^2.13.0",
"koa-body": "^4.2.0",
"lodash": "^4.17.21",
"marked": "^2.1.3",
"mongodb": "^3.6.2",
"mongoose": "^5.13.3",
"pino": "^6.7.0",
Expand All @@ -40,9 +43,12 @@
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@types/chai": "^4.2.12",
"@types/config": "0.0.36",
"@types/highlight.js": "^10.1.0",
"@types/katex": "^0.11.1",
"@types/koa": "^2.11.4",
"@types/koa__router": "^8.0.2",
"@types/lodash": "^4.14.171",
"@types/marked": "^2.0.4",
"@types/mocha": "^8.0.3",
"@types/mongoose": "^5.11.97",
"@types/node": "^14.11.2",
Expand Down
75 changes: 75 additions & 0 deletions backend/src/common/markdown/MarkedUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import _ from 'lodash';
import marked, { Tokenizer, TokensList } from 'marked';
import hljs from 'highlight.js';
import katex from 'katex';

export const renderContent = (rawContent: string) => {
const MATH: string = 'Math';

const renderer = {
code(code, infostring) {
return infostring === MATH
? katex.renderToString(code, { throwOnError: false, displayMode: true })
: false;
},
};

const tokenizer: Tokenizer = {
// @ts-ignore
inlineText(src) {
const cap = src.match(/(\s*)([^$\n]*)\$([^$\n]+)\$([^$\n]*)/);
return _.isNil(cap)
? false
: {
type: 'text',
raw: cap[0],
text: cap[2] + katex.renderToString(cap[3].trim(), { throwOnError: false }) + cap[4],
};
},

// @ts-ignore
fences(src) {
const cap = src.match(
/^ {0,3}(`{3,}|\${2,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`$]* *(?:\n+|$)|$)/,
);
return _.isNil(cap)
? false
: {
type: 'code',
raw: cap[0],
codeBlockStyle: 'indented',
lang: cap[1] === '$$' ? MATH : cap[2].trim(),
text: cap[3],
};
},
};

marked.use({ tokenizer, renderer });
marked.setOptions({
breaks: true,
highlight: (code, lang) => {
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
return `<pre class="hljs"><code class = "language-${language} hljs">${hljs.highlight(code, { language }).value}</code></pre>`;
},
});
const tokenList: TokensList = marked.lexer(rawContent);
tokenList
.forEach((token, idx) => {
if (token.type === 'table') {
const tableHtml: string = marked.parser([token] as TokensList)
.replace(/align="left"/ig, 'style="text-align:left;"')
.replace(/align="right"/ig, 'style="text-align:right;"')
.replace(/align="center"/ig, 'style="text-align:center;"')
.replace(/align="justify"/ig, 'style="text-align:justify;"');
tokenList[idx] = {
type: 'html',
raw: tableHtml,
pre: false,
text: tableHtml,
};
}
});

// TODO: tokenList로부터 TOC 추출
return marked.parser(tokenList);
};
8 changes: 2 additions & 6 deletions backend/src/post/PostService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import PostRepository from '@src/post/repository/PostRepository';
import CategoryRepository from '@src/category/CategoryRepository';
import SeriesRepository from '@src/series/SeriesRepository';
import TagRepository from '@src/tag/TagRepository';
import { renderContent } from '@src/common/markdown/MarkedUtil';
import { CreatePostMetaRepoParamDto } from '@src/post/dto/PostMetaRepoParamDto';
import { CreateNewPostParamDto } from '@src/post/dto/PostParamDto';
import { CreatePostRepoParamDto } from '@src/post/dto/PostRepoParamDto';
Expand Down Expand Up @@ -72,7 +73,7 @@ export default class PostService {
private makeCreatePostRepoParamDto(postNo: number, paramDto: CreateNewPostParamDto, currentDate: Date): CreatePostRepoParamDto {
const { post } = paramDto;
const rawContent: string = this.readPostContent(post.path);
const renderedContent = this.renderContent(rawContent);
const renderedContent = renderContent(rawContent);
const createPostRepoParamDto: CreatePostRepoParamDto = {
postNo,
title: post.name as string,
Expand All @@ -92,9 +93,4 @@ export default class PostService {
private readPostContent(path: string): string {
return fs.readFileSync(path).toString();
}

private renderContent(rawContent: string): string {
// TODO: 렌더링 로직 구현
return rawContent;
}
}
21 changes: 21 additions & 0 deletions backend/test/TestUtil.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ClientSession } from 'mongoose';
import * as TransactionUtil from '@src/common/mongodb/TransactionUtil';
import BlogError from '@src/common/error/BlogError';
import { appPath } from '@test/data/testData';
import fs from 'fs';
import { File, FileJSON } from 'formidable';

export const replaceUseTransactionForTest = async (sandbox, session: ClientSession): Promise<any> => sandbox.replace(
TransactionUtil,
Expand Down Expand Up @@ -42,3 +45,21 @@ export const errorShouldBeThrown = async (errorShouldBe: Error, callback: Functi
}
isAnyErrorThrown.should.be.true;
};

export const extractFileInfoFromRawFile = (fileName: string): { file: File, fileContent: string } => {
const filePath = `${appPath.testData}/${fileName}`;
const fileStream: Buffer = fs.readFileSync(filePath);
const fileContent: string = fileStream.toString();
const fileStat: fs.Stats = fs.statSync(filePath);
const file: File = {
size: fileStream.byteLength,
path: filePath,
name: fileName,
type: 'application/octet-stream',
lastModifiedDate: fileStat.mtime,
toJSON(): FileJSON {
return {} as FileJSON;
},
};
return { file, fileContent };
};
Loading

0 comments on commit 65e644e

Please sign in to comment.