Spaces:
Sleeping
Sleeping
| /** | |
| * 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, '<br/>\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을 <br/>로 표시 한다. | |
| 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 `<pre style="width: 100%; background-color: lightgray;"><code class="${infostring}">${code}</code></pre>`; | |
| } | |
| // blockquote(string quote) | |
| renderer.blockquote = function(quote) { | |
| return `<blockquote>${quote}</blockquote>`; | |
| } | |
| // html(string html) : Markdown에 직접 사용된 html 코드 | |
| renderer.html = function(html) { | |
| return `${html}`; | |
| } | |
| // hr() | |
| renderer.hr = function() { | |
| return '<hr/>'; | |
| } | |
| // checkbox(boolean checked) | |
| renderer.checkbox = function(checked) { | |
| if (checked) { | |
| return '<input checked disabled type="checkbox">'; | |
| } else { | |
| return '<input disabled type="checkbox">'; | |
| } | |
| } | |
| // paragraph(string text) | |
| renderer.paragraph = function(text) { | |
| return `<p>${text}</p>`; | |
| } | |
| // table(string header, string body) | |
| renderer.table = function(header, body) { | |
| // return `<table border="1"><thead>${header}</thead><tbody>${body}</tbody></table>`; | |
| return `<table class="table table-bordered table-hover table-sm markdown" style="${config.theme.font.size}"><thead>${header}</thead><tbody>${body}</tbody></table>`; | |
| // return `<table class="table table-hover table-sm markdown" style="${config.theme.font.size}"><thead>${header}</thead><tbody>${body}</tbody></table>`; | |
| } | |
| // tablerow(string content) | |
| renderer.tablerow = function(content) { | |
| return `<tr>${content}</tr>`; | |
| // return `<tr class="table-primary">${content}</tr>`; | |
| } | |
| // tablecell(string content, object flags) | |
| // flags = { | |
| // header: true || false, | |
| // align: 'center' || 'left' || 'right' | |
| // } | |
| renderer.tablecell = function(content, flags) { | |
| if (flags.header) { | |
| return `<th style="text-align: center;">${content}</th>`; | |
| } else { | |
| if (flags.align == null) { | |
| return `<td>${content}</td>`; | |
| } else { | |
| return `<td style="text-align: ${flags.align};">${content}</td>`; | |
| } | |
| } | |
| } | |
| //--- Inline level renderer methods | |
| // strong(string text) | |
| renderer.strong = function(text) { | |
| return `<strong>${text}</strong>`; | |
| } | |
| // em(string text) | |
| renderer.em = function(text) { | |
| return `<em>${text}</em>`; | |
| } | |
| // codespan(string code) | |
| renderer.codespan = function(code) { | |
| return `<code>${code}</code>`; | |
| } | |
| // br() | |
| renderer.br = function() { | |
| return '<br/>'; | |
| } | |
| // del(string text) | |
| renderer.del = function(text) { | |
| return `<del>${text}</del>`; | |
| } | |
| // heading(string text, number level, string raw, Slugger slugger) | |
| renderer.heading = function(text, level, raw, slugger) { | |
| let uniqueId = slugger.slug(text); | |
| // return `<h${level} id="${uniqueId}">${text}</h${level}>`; | |
| if (level <= 2) { | |
| return `<br/><h${level} id="${uniqueId}">${text}</h${level}><hr/>`; | |
| } else { | |
| return `<br/><h${level} id="${uniqueId}">${text}</h${level}>`; | |
| } | |
| } | |
| // list(string body, boolean ordered, number start) | |
| renderer.list = function(body, ordered, start) { | |
| if (ordered) { | |
| //--- <ol type="1, I, i, A, a" reversed> | |
| return `<ol start="${start}">${body}</ol>`; | |
| } else { | |
| //--- <ul type="square"> | |
| return `<ul>${body}</ul>`; | |
| } | |
| } | |
| // listitem(string text, boolean task, boolean checked) | |
| renderer.listitem = function(text, task, checked) { | |
| return `<li>${text}</li>`; | |
| } | |
| // link(string href, string title, string text) | |
| renderer.link = function(href, title, text) { | |
| let txtParam = ''; | |
| href = href.replace(/ /g, '%20'); | |
| if (href.startsWith('http')) { | |
| return `<a ${txtParam} href="${href}" target="_blank">${text}</a>`; | |
| } else if (href.startsWith('mailto')) { | |
| return `<a ${txtParam} href="${href}">${text}</a>`; | |
| } else if (href.startsWith('tel')) { | |
| return `<a ${txtParam} href="${href}">${text}</a>`; | |
| } else { | |
| let target = ''; | |
| if (text.endsWith(' ')) { | |
| target = 'target="_blank"'; | |
| text = text.trim(); | |
| } | |
| if (href.endsWith('.md')) { | |
| let baseUrl = config.http.baseUrl + config.http.path.replace(/\//g, ''); | |
| if (href.startsWith('/')) { | |
| return `<a ${txtParam} href="${baseUrl}${href}" ${target}>${text}</a>`; | |
| } else { | |
| return `<a ${txtParam} href="${baseUrl}${options.folder}/${href}" ${target}>${text}</a>`; | |
| } | |
| } else { | |
| if (href.startsWith('record=')) { | |
| return `<a ${txtParam} href="${config.http.baseUrl}${config.http.path.replace(/\//g, '')}/markdowns?action=convert&${href}" ${target}>${text}</a>`; | |
| } else if (href.startsWith('/')) { | |
| if (href.startsWith('/${config.http.path}')) { | |
| return `<a ${txtParam} href="${config.http.path}${href.substring(20)}" ${target}>${text}</a>`; | |
| } else { | |
| return `<a ${txtParam} href="${options.baseUrl}${href}" ${target}>${text}</a>`; | |
| } | |
| } else { | |
| return `<a ${txtParam} href="${options.baseUrl}${options.folder}/${href}" ${target}>${text}</a>`; | |
| } | |
| } | |
| } | |
| } | |
| // image(string href, string title, string text) | |
| renderer.image = function(href, title, text) { | |
| if (href.startsWith('http')) { | |
| return `<img src="${href}" alt="${text}">`; | |
| } else { | |
| let width = getWidth(href); | |
| let myBaseUrl = config.http.baseUrl.substring(0, config.http.baseUrl.length - 1); | |
| if (href.startsWith('/')) { | |
| return `<img src="${myBaseUrl}${options.baseUrl}${href}" alt="${text}" style="${width}">`; | |
| } else { | |
| return `<img src="${myBaseUrl}${options.baseUrl}${options.folder}/${href}" alt="${text}" style="${width}">`; | |
| } | |
| } | |
| } | |
| // text(string text) | |
| renderer.text = function(text) { | |
| return `${text}`; | |
| } | |
| return renderer; | |
| } | |
| } | |
| module.exports = Markdown; | |