'use strict' /** * Copyright (c) 2017~2021, OBCon Inc. * All rights reserved. */ /** * @file * @copyright 2017~2021, OBCon Inc. * @author gye hyun james kim [pnuskgh@gmail.com] */ let fs = require('fs'); let path = require('path'); let marked = require('marked'); let highlightjs = require('highlightjs'); //--- https://github.com/markedjs/marked //--- https://marked.js.org/ //--- https://highlightjs.org/ //--- https://www.hahwul.com/2019/02/editorjs-markdown-javascript-library.html //--- Page 모듈에서 사용중 class Markdown { constructor() { this._req = null; //--- HTTP request this._language = 'ko_KR'; //--- 언어 this._page = config.theme.homepage; //--- Page this._config = null; //--- Page에 해당하는 설정 this._file = null; //--- Page에 해당하는 파일 this._content = null; //--- Page에 해당하는 파일의 내용 this._folderUrl = null; //--- Page에 해당하는 파일이 있는 폴더의 URL } init(req, res) { this._req = req || null; this._language = req.query.language || 'ko_KR'; this.page = req.query.page || config.theme.homepage; } get language() { return this._language; } set language(newValue) { this._language = newValue; } get page() { return this._page; } set page(newValue) { this._page = newValue; this._config = null; this._file = null; this._content = null; this._folderUrl = null; } //--- Config 파일 구조 //--- 주의 : 적용할 설정 파일 : 발견한 Markdown 파일을 기준으로 설정을 적용 한다. //--- folder(this.file)/config.js + ${file}.js //--- config 파일 내용은 this._defaultConfig 참조 get config() { if (this._config == null) { this._config = this._getConfig(); } return this._config; } set config(newValue) { this._config = newValue; } get file() { if (this._file == null) { this._file = this._getFile(); } return this._file; } set file(newValue) { this._file = newValue; } get content() { if (this._content == null) { this._content = this._getContent(); } return this._content; } set content(newValue) { this._content = newValue; } get folderUrl() { if (this._folderUrl == null) { this._folderUrl = this._getFolderUrl(); } return this._folderUrl; } set folderUrl(newValue) { this._folderUrl = newValue; } _getConfig() { let config = { layout: 'layout.ejs', //--- layout isPublic: false //--- false. 로그인 필요 }; let tmpFile = path.join(path.dirname(this.file), 'config.js'); if (fs.existsSync(tmpFile)) { delete require.cache[tmpFile]; let conf = require(tmpFile); Object.assign(config, conf); } tmpFile = path.join(path.dirname(this.file), path.basename(this.file, '.md') + '.js'); if (fs.existsSync(tmpFile)) { delete require.cache[tmpFile]; let conf = require(tmpFile); Object.assign(config, conf); } return config; } //--- Markdown 파일 구조 //--- this._page = /${folder}/${file}.md //--- manual/${service}/site_${siteID}/${lang}/this._page //--- manual/${service}/default/${lang}/this._page //--- manual/${service}/site_${site}/ko_KR/this._page //--- manual/${service}/default/ko_KR/this._page //--- 적용할 설정 파일 : 발견한 Markdown 파일을 기준으로 설정을 적용 한다. //--- 적용할 메뉴 파일 : 발견한 Markdown 파일을 기준으로 메뉴를 적용 한다. _getFile() { let files = []; if (this._req.session.isLogined) { files.push(utils.getPath('manual', config.service.name, 'site_' + this._req.session.site.id, this._language, this._page)); files.push(utils.getPath('manual', config.service.name, 'default', this._language, this._page)); files.push(utils.getPath('manual', config.service.name, 'site_' + this._req.session.site.id, 'ko_KR', this._page)); files.push(utils.getPath('manual', config.service.name, 'default', 'ko_KR', this._page)); } else { files.push(utils.getPath('manual', config.service.name, 'default', this._language, this._page)); files.push(utils.getPath('manual', config.service.name, 'default', 'ko_KR', this._page)); } for (let idx = 0; idx < files.length; idx++) { if (fs.existsSync(files[idx])) { return files[idx].replace(/\\/g, '/'); } } return files[0].replace(/\\/g, '/'); } _getContent() { let content = ''; if ((this.file == null) || (fs.existsSync(this.file) == false)) { logger.error('File not found: ' + this.file); } else { content = fs.readFileSync(this.file, "utf8"); } return content; } _getFolderUrl() { let folderUrl = this.file; folderUrl = folderUrl.substring(folderUrl.indexOf('/manual')); folderUrl = folderUrl.substring(0, folderUrl.length - this._page.length); return config.http.path + folderUrl; } //--- 일반적인 HTML 페이지를 반환 한다. // options = { // target: 표시할 Content 지정 (menu, source, content) // format: 변환한 후의 양식 (html, pdf(Reserved)) // content: target이 content일 때, markdown 내용을 담은 문자열 // filename: format이 pdf일 때, 저장할 파일 이름 // } getHtml() { let content = this.content.replace(/\r\n/g, '\n').replace(/ \n/g, '
\n'); let options = { baseUrl: this.folderUrl, folder: path.dirname(this._page) }; marked.setOptions({ baseUrl: config.http.baseUrl + config.http.path.replace(/\//g, '') + '/manual/' + config.service.name + '/' + this._language, breaks: false, //--- true. \n을
로 표시 한다. gfm: true, headerIds: true, headerPrefix: '', highlight: function(code) { return highlightjs.highlightAuto(code).value; }, langPrefix: '', //--- code에서 언어의 prefix mangle: true, pedantic: false, renderer: this.renderHtml(options), sanitize: false, sanitizer: null, silent: false, smartLists: true, smartypants: false, xhtml: false }); return (typeof(marked.marked) == 'undefined') ? marked(content):marked.marked(content); } renderHtml(options) { let renderer = new marked.Renderer(); //--- 이미지 이름에서 __ 뒤에 있는 문자열로 넓이를 계산 한다. function getWidth(href) { let tmpStr = path.basename(href); if (tmpStr.lastIndexOf('__') == -1) { return ''; } tmpStr = (-1 < tmpStr.indexOf('.')) ? tmpStr.substring(0, tmpStr.indexOf('.')):tmpStr; tmpStr = tmpStr.substring(tmpStr.lastIndexOf('__') + 2); if (tmpStr.endsWith('p')) { tmpStr = tmpStr.substring(0, tmpStr.length - 1) + '%'; } return 'width: ' + tmpStr + ';'; } //--- Block level renderer methods // code(string code, string infostring, boolean escaped) renderer.code = function(code, infostring, escaped) { ///--- infostring : 사용 언어 return `
${code}
`; } // blockquote(string quote) renderer.blockquote = function(quote) { return `
${quote}
`; } // html(string html) : Markdown에 직접 사용된 html 코드 renderer.html = function(html) { return `${html}`; } // hr() renderer.hr = function() { return '
'; } // checkbox(boolean checked) renderer.checkbox = function(checked) { if (checked) { return ''; } else { return ''; } } // paragraph(string text) renderer.paragraph = function(text) { return `

${text}

`; } // table(string header, string body) renderer.table = function(header, body) { // return `${header}${body}
`; return `${header}${body}
`; // return `${header}${body}
`; } // tablerow(string content) renderer.tablerow = function(content) { return `${content}`; // return `${content}`; } // tablecell(string content, object flags) // flags = { // header: true || false, // align: 'center' || 'left' || 'right' // } renderer.tablecell = function(content, flags) { if (flags.header) { return `${content}`; } else { if (flags.align == null) { return `${content}`; } else { return `${content}`; } } } //--- Inline level renderer methods // strong(string text) renderer.strong = function(text) { return `${text}`; } // em(string text) renderer.em = function(text) { return `${text}`; } // codespan(string code) renderer.codespan = function(code) { return `${code}`; } // br() renderer.br = function() { return '
'; } // del(string text) renderer.del = function(text) { return `${text}`; } // heading(string text, number level, string raw, Slugger slugger) renderer.heading = function(text, level, raw, slugger) { let uniqueId = slugger.slug(text); // return `${text}`; if (level <= 2) { return `
${text}
`; } else { return `
${text}`; } } // list(string body, boolean ordered, number start) renderer.list = function(body, ordered, start) { if (ordered) { //---
    return `
      ${body}
    `; } else { //---