Spaces:
Sleeping
Sleeping
| /** | |
| * Copyright (c) 2017~2022, OBCon Inc. | |
| * All rights reserved. | |
| */ | |
| /** | |
| * @file | |
| * @copyright 2017~2022, OBCon Inc. | |
| * @author gye hyun james kim [pnuskgh@gmail.com] | |
| */ | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const marked = require('marked'); | |
| const 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 | |
| //--- Markdown 모듈과 openapi/deploy.js 파일에서 사용중 | |
| class Markdown { | |
| constructor() { | |
| this._isDebug = false; | |
| this._options = null; //--- 옵션 | |
| this._file = null; //--- Markdown 파일 | |
| this._config = null; //--- Markdown 파일을 위한 설정 | |
| this._content = null; //--- Markdown 파일 내용 | |
| this._baseUrl = null; //--- Markdown 파일의 baseUrl | |
| this._service = null; //--- 강제로 지정한 서비스 이름 | |
| } | |
| debug(...messages) { | |
| if (this._isDebug) { | |
| console.log(...messages) | |
| } | |
| } | |
| initialize(options={}) { | |
| this._options = Object.assign({ | |
| req: null, //--- Express.Request | |
| res: null, //--- Express.Response | |
| language: 'ko_KR', //--- 사용 언어 | |
| file: 'README.md', //--- Markdown 파일 | |
| service: null //--- 강제로 지정한 서비스 이름 | |
| }, options); | |
| this._file = null; //--- Markdown 파일 | |
| this._config = null; //--- Markdown 파일을 위한 설정 | |
| this._content = null; //--- Markdown 파일 내용 | |
| this._baseUrl = null; //--- Markdown 파일의 baseUrl | |
| this._service = null; //--- 강제로 지정한 서비스 이름 | |
| } | |
| get options() { | |
| return this._options; | |
| } | |
| get file() { | |
| if (this._file == null) { | |
| this._file = this._getFile(); | |
| } | |
| return this._file; | |
| } | |
| //--- Markdown 파일 구조 | |
| //--- this.options.file = /${folder}/${file}.md | |
| //--- manual/${service}/site_${siteID}/${lang}/this.options.file | |
| //--- manual/${service}/site_${site}/ko_KR/this.options.file | |
| //--- manual/${service}/default/${lang}/this.options.file | |
| //--- manual/${service}/default/ko_KR/this.options.file | |
| _getFile() { | |
| const file = this.options.file; | |
| const files = []; | |
| if ((this.options.req) && (this.options.req.session) && (this.options.req.session.isLogined)) { | |
| files.push(utils.getPath('manual', config.service.name, 'site_' + this.options.req.session.site.id, this.options.language, file)); | |
| files.push(utils.getPath('manual', config.service.name, 'site_' + this.options.req.session.site.id, 'ko_KR', file)); | |
| files.push(utils.getPath('manual', config.service.name, 'default', this.options.language, file)); | |
| files.push(utils.getPath('manual', config.service.name, 'default', 'ko_KR', file)); | |
| } else { | |
| files.push(utils.getPath('manual', config.service.name, 'default', this.options.language, file)); | |
| files.push(utils.getPath('manual', config.service.name, 'default', 'ko_KR', file)); | |
| } | |
| for (let idx = 0; idx < files.length; idx++) { | |
| if (fs.existsSync(files[idx])) { | |
| return files[idx].replace(/\\/g, '/'); | |
| } | |
| } | |
| return files[0].replace(/\\/g, '/'); | |
| } | |
| //--- Menu 파일 종류 | |
| //--- 로그인 전 : MENU_PUBLIC.md | |
| //--- 로그인 후 : MENU_PRIVATE.md | |
| //--- Menu 파일 구조 | |
| //--- 적용할 메뉴 파일 : 발견한 Markdown 파일을 기준으로 메뉴를 적용 한다. | |
| //--- folder(this._file)/${menufile}.md, 상위 폴더로 순환 | |
| _getMenuFile() { | |
| let menuFile = ((this.options.req != null) && (this.options.req.session.isLogined)) ? 'MENU_PRIVATE.md':'MENU_PUBLIC.md'; | |
| menuFile = this.config.menuFile || menuFile; | |
| const files = []; | |
| const manualFolder = path.join(config.folder_root, 'manual', config.service.name); | |
| let menuFolder = path.dirname(this.file); | |
| while (manualFolder.length <= menuFolder.length) { | |
| files.push(path.join(menuFolder, menuFile)); | |
| menuFolder = path.dirname(menuFolder); | |
| } | |
| let rtMenuFile = files[0]; | |
| for (let idx = 0; idx < files.length; idx++) { | |
| if (fs.existsSync(files[idx])) { | |
| rtMenuFile = files[idx] | |
| break; | |
| } | |
| } | |
| return rtMenuFile; | |
| } | |
| get config() { | |
| if (this._config == null) { | |
| this._config = this._getConfig_new(); | |
| // this._config = this._getConfig(); | |
| } | |
| return this._config; | |
| } | |
| //--- 사용하지 않음 | |
| // _getConfig() { | |
| // let rtConfig = this._getDefaultConfig(); | |
| // [ | |
| // path.join(path.dirname(this.file), 'config.js'), | |
| // path.join(path.dirname(this.file), path.basename(this.file, '.md') + '.js') | |
| // ].forEach(file => { | |
| // if (fs.existsSync(file)) { | |
| // console.log('getConfig: file exist'); | |
| // delete require.cache[file]; | |
| // const conf = require(file); | |
| // Object.assign(rtConfig, conf); | |
| // } | |
| // }); | |
| // return rtConfig; | |
| // } | |
| _getConfig_new() { | |
| const file = path.basename(this.options.file, '.md') + '.js'; | |
| const files = []; | |
| if ((this.options.req) && (this.options.req.session) && (this.options.req.session.isLogined)) { | |
| files.push(utils.getPath('manual', config.service.name, 'site_' + this.options.req.session.site.id, this.options.language, file)); | |
| files.push(utils.getPath('manual', config.service.name, 'site_' + this.options.req.session.site.id, this.options.language, 'config.js')); | |
| files.push(utils.getPath('manual', config.service.name, 'site_' + this.options.req.session.site.id, 'ko_KR', file)); | |
| files.push(utils.getPath('manual', config.service.name, 'site_' + this.options.req.session.site.id, 'ko_KR', 'config.js')); | |
| files.push(utils.getPath('manual', config.service.name, 'default', this.options.language, file)); | |
| files.push(utils.getPath('manual', config.service.name, 'default', this.options.language, 'config.js')); | |
| files.push(utils.getPath('manual', config.service.name, 'default', 'ko_KR', file)); | |
| files.push(utils.getPath('manual', config.service.name, 'default', 'ko_KR', 'config.js')); | |
| } else { | |
| files.push(utils.getPath('manual', config.service.name, 'default', this.options.language, file)); | |
| files.push(utils.getPath('manual', config.service.name, 'default', this.options.language, 'config.js')); | |
| files.push(utils.getPath('manual', config.service.name, 'default', 'ko_KR', file)); | |
| files.push(utils.getPath('manual', config.service.name, 'default', 'ko_KR', 'config.js')); | |
| } | |
| let rtConfig = this._getDefaultConfig(); | |
| for (let idx = 0; idx < files.length; idx++) { | |
| const file = files[idx]; | |
| if (fs.existsSync(file)) { | |
| // console.log('getConfig: file exist', file); | |
| delete require.cache[file]; | |
| const conf = require(file); | |
| Object.assign(rtConfig, conf); | |
| break; | |
| } | |
| } | |
| return rtConfig; | |
| } | |
| _getDefaultConfig() { | |
| return { | |
| layout: config.theme.layout, //--- layout | |
| isPublic: false, //--- false. 로그인 필요 | |
| menuFile: null, //--- 메뉴 파일 | |
| showIndex: true, //--- Reserved: 페이지 상단에 목차 표시 (Default. true) | |
| prev: null, //--- Reserved: 이전 페이지 (Default. null) | |
| next: null //--- Reserved: 다음 페이지 (Default. null) | |
| }; | |
| } | |
| get content() { | |
| if (this._content == null) { | |
| this._content = this._getContent(); | |
| } | |
| return this._content; | |
| } | |
| _getContent() { | |
| if ((this._file == null) || (fs.existsSync(this._file) == false)) { | |
| logger.error('File not found: ' + this._file); | |
| return ''; | |
| } else { | |
| return fs.readFileSync(this._file, "utf8"); | |
| } | |
| } | |
| get baseUrl() { | |
| if (this._baseUrl == null) { | |
| this._baseUrl = this._getBaseUrl(); | |
| } | |
| return this._baseUrl; | |
| } | |
| _getBaseUrl() { | |
| let baseUrl = this.file; | |
| baseUrl = baseUrl.substring(baseUrl.indexOf('/manual')); | |
| baseUrl = baseUrl.substring(0, baseUrl.length - this.options.file.length); | |
| return utils.link.join(config.http.path, baseUrl); | |
| } | |
| get service() { | |
| if (this._service == null) { | |
| this._service = this._getService(); | |
| } | |
| return this._service; | |
| } | |
| _getService() { | |
| let service = this.options.service || ''; | |
| if (service == '') { | |
| //--- Parameter로 전달된 servcie 값이 없으면 세션에 저장된 service 값을 사용 한다. | |
| //--- service 값이 없어도 이전에 지정한 service 값으로 화면을 조회 한다. | |
| if ((this.options.req) && (this.options.req.session) && (this.options.req.session.service)) { | |
| service = this.options.req.session.service; | |
| } | |
| } | |
| if (service == '') { | |
| service = config.service.name; | |
| } | |
| //--- 세션에 service 이름을 저장 한다. | |
| if ((this.options.req) && (this.options.req.session) && (this.options.req.session.service != service)) { | |
| this.options.req.session.service = service; | |
| this.options.req.session.save(function(err) { }); | |
| } | |
| return service; | |
| } | |
| // //--- Markdown 파일을 HTML 페이지로 변환하여 반환 한다. | |
| // // options = { | |
| // // target: 표시할 Content 지정 (menu, source, content) | |
| // // format: 변환한 후의 양식 (html, pdf(Reserved)) | |
| // // content: target이 content일 때, markdown 내용을 담은 문자열 | |
| // // filename: format이 pdf일 때, 저장할 파일 이름 | |
| // // } | |
| // getHtml() { | |
| // // const parserHtml = this._getHtmlParser({ | |
| // // baseUrl: this.baseUrl, | |
| // // folder: path.dirname(this.options.file) | |
| // // }); | |
| // // //--- http://local.bluestones.biz:90/cms/manual/service_cms_bluestone/default/ko_KR/${config.http.path}/invests | |
| // // const baseUrl = config.http.baseUrl + config.http.path + `/manual/${this.service}/${this.options.language}`; | |
| // // this._setMarkedOptions(baseUrl, parserHtml); | |
| // // return this.parseMarkdown(this.content); | |
| // return this.convert({ | |
| // baseUrl: this.baseUrl, | |
| // folder: path.dirname(this.options.file) | |
| // }); | |
| // } | |
| // options = { | |
| // source: ~ | |
| // content: ~ | |
| // target: ~ | |
| // } | |
| convert(options={}) { | |
| options = Object.assign({ | |
| language: this.options.language, //--- 언어 (ko_KR, en_US, ...) | |
| folder: path.dirname(this.options.file), //--- Markdown 파일의 폴더 | |
| file: this.options.file, //--- Markdown 파일 이름 | |
| source: options.source || 'file', //--- 원본 : file. file 사용, menu. 메뉴 file 사용, content. content 사용 | |
| content: options.content || '', //--- target이 content인 경우 markdown 문서를 저장 | |
| //--- 변환 : html. HTML 반환, index. 인덱스 반환, menu. 메뉴 HTML 반환, info: 정보 반환 | |
| target: options.target || 'html', | |
| baseUrl: options.baseUrl || this.baseUrl //--- link와 image에서 사용하여 prefix URL | |
| // //--- Target 양식 : source. 원본 반환, index. 인덱스 반환, menu. 메뉴 반환, content. ??? | |
| // target: options.target || 'source', | |
| //--- Output 포맷 : html. HTML로 반환. pdf. PDF로 변환 (Reserved) | |
| // format: options.format || 'html', | |
| // //--- format이 pdf일 때, 저장할 파일 이름 (Reserved) | |
| // // filename: options.filename || 'noname.pdf', | |
| }, options); | |
| // const baseUrl = config.http.baseUrl + config.http.path + '/manual/' + config.service.name + '/' + this.options.language; | |
| const baseUrl = utils.link.join(config.http.baseUrl, config.http.path, 'manual', config.service.name, + this.options.language); | |
| switch(options.target) { | |
| case 'index': //--- Markdown Page Index | |
| this._setMarkedOptions(baseUrl, this._getIndexParser(options)); | |
| break; | |
| case 'menu': //--- 문서 목차 | |
| this._setMarkedOptions(baseUrl, this._getMenuParser(options)); | |
| break; | |
| case 'html': //--- Markdown을 HTML로 변환 | |
| this._setMarkedOptions(baseUrl, this._getHtmlParser(options)); | |
| break; | |
| case 'info': | |
| return { | |
| filename: this.file | |
| }; | |
| break; | |
| default: | |
| break; | |
| } | |
| const content = this.getContent(options); | |
| return this._parseMarkdown(modules.markdowns.lang.replaceContent(content, this.service)); | |
| } | |
| getContent(options) { | |
| switch (options.source) { | |
| case 'content': | |
| return options.content; | |
| case 'menu': | |
| return this._getContentFromFile(this._getMenuFile()); | |
| case 'file': | |
| default: | |
| return this._getContentFromFile(this.file); | |
| } | |
| } | |
| //--- https://marked.js.org/using_advanced | |
| //--- https://marked.js.org/using_pro | |
| _setMarkedOptions(baseUrl, renderer) { | |
| marked.setOptions({ | |
| baseUrl: baseUrl.replace('//', '/'), | |
| renderer: renderer, | |
| highlight: function(code, lang) { | |
| // const language = highlightjs.getLanguage(lang) ? lang : 'plaintext'; | |
| // return highlightjs.highlight(code, { language }).value; | |
| return highlightjs.highlightAuto(code).value; | |
| }, | |
| langPrefix: 'language-', //--- code에서 언어의 prefix | |
| breaks: false, //--- true. \n을 <br/>로 표시 한다. | |
| gfm: true, | |
| headerIds: true, | |
| headerPrefix: '', | |
| mangle: true, | |
| pedantic: false, | |
| sanitize: false, | |
| sanitizer: null, | |
| silent: false, | |
| smartLists: true, | |
| smartypants: false, | |
| xhtml: false | |
| }); | |
| } | |
| _parseMarkdown(content) { | |
| let rtContent = content.replace(/\r\n/g, '\n'); | |
| rtContent = (typeof(marked.marked) == 'undefined') ? marked(rtContent):marked.marked(rtContent); | |
| //--- To-Do : windows 환경에서 marked의 경우 다음 오류가 발생 한다. | |
| //--- "## Sitemap\n \n" #--- 비정상 표시 | |
| //--- "## Sitemap\n\n" #--- 정상 표시 | |
| // const contentPre = (typeof(marked.marked) == 'undefined') ? marked(this._contentPre(content)):marked.marked(this._contentPre(content)); | |
| // if (format == 'html') { | |
| // rtContent = rtContent.replace(/\n \n -/g, '\n -'); | |
| // //--- ' \n'을 '<br/>\n'으로 변환 한다. | |
| // return rtContent.replace(/ \n/g, '<br/>\n'); | |
| // } | |
| return rtContent; | |
| } | |
| _getNoneParser() { | |
| const renderer = new marked.Renderer(); | |
| const funcNone = () => ''; | |
| renderer.code = funcNone; | |
| renderer.blockquote = funcNone; | |
| renderer.html = funcNone; | |
| renderer.heading = funcNone; | |
| renderer.hr = funcNone; | |
| renderer.list = funcNone; | |
| renderer.listitem = funcNone; | |
| renderer.checkbox = funcNone; | |
| renderer.paragraph = funcNone; | |
| renderer.table = funcNone; | |
| renderer.tablerow = funcNone; | |
| renderer.tablecell = funcNone; | |
| renderer.strong = (text) => `<strong>${text}</strong>`; | |
| renderer.em = funcNone; | |
| renderer.codespan = funcNone; | |
| renderer.br = funcNone; | |
| renderer.del = funcNone; | |
| renderer.link = funcNone; | |
| renderer.image = funcNone; | |
| renderer.text = (text) => text; | |
| return renderer; | |
| } | |
| //--- Web_Publishing.md 파일 참조 | |
| _getHtmlParser(options) { | |
| const debug = this.debug.bind(this); | |
| const renderer = new marked.Renderer(); | |
| //--- Block level renderer methods | |
| // code(string code, string infostring, boolean escaped) | |
| //--- infostring : 사용 언어 | |
| renderer.code = (code, infostring, escaped) => `<pre style="width: 100%; background-color: lightgray;"><code class="${infostring}">${code}</code></pre>`; | |
| renderer.blockquote = (quote) => `<blockquote>${quote}</blockquote>`; | |
| renderer.html = (html) => html; | |
| let headingIndex = 0; | |
| // heading(string text, number level, string raw, Slugger slugger) | |
| renderer.heading = function(text, level, raw, slugger) { | |
| headingIndex = headingIndex + 1; | |
| // const uniqueId = slugger.slug(text); | |
| const uniqueId = `heading_${headingIndex}`; | |
| // 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}>`; | |
| } | |
| } | |
| renderer.hr = () => '<hr/>'; | |
| // list(string body, boolean ordered, number start) | |
| let index_list = 0; | |
| renderer.list = function(body, ordered, start) { | |
| index_list = index_list + 1; | |
| if (ordered) { | |
| //--- <ol type="1, I, i, A, a" reversed> | |
| return `<ol id="list_${index_list}" start="${start}">${body}</ol>`; | |
| } else { | |
| //--- <ul type="square"> | |
| return `<ul id="list_${index_list}">${body}</ul>`; | |
| } | |
| } | |
| // listitem(string text, boolean task, boolean checked) | |
| renderer.listitem = function(text, task, checked) { | |
| return `<li>${text}</li>`; | |
| } | |
| renderer.checkbox = (checked) => `<input ${(checked) ? 'checked':''} disabled type="checkbox">`; | |
| renderer.paragraph = (text) => `<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>`; | |
| } | |
| renderer.tablerow = (content) => `<tr>${content}</tr>`; | |
| // tablecell(string content, object flags) | |
| // flags = { | |
| // header: true || false, | |
| // align: 'center' || 'left' || 'right' | |
| // } | |
| renderer.tablecell = function(content, flags) { | |
| //--- To-Do : content에 mardown 파싱 도입 검토 | |
| if (flags.header) { | |
| return `<th style="text-align: center;">${content}</th>`; | |
| } else { | |
| const align = (flags.align == null) ? '': `style="text-align: ${flags.align};`; | |
| return `<td ${align}">${content}</td>`; | |
| } | |
| } | |
| //--- Inline level renderer methods | |
| // strong(string text) | |
| renderer.strong = (text) => `<strong>${text}</strong>`; | |
| renderer.em = (text) => `<em>${text}</em>`; | |
| renderer.codespan = (code) => `<code>${code}</code>`; | |
| renderer.br = () => '<br/>'; | |
| renderer.del = (text) => `<del>${text}</del>`; | |
| // link(string href, string title, string text) | |
| //--- href 규칙 | |
| //--- ~' ' : 새창에서 표시 | |
| renderer.link = function(href, title, text) { | |
| let attrs = ''; | |
| //--- herf | target | |
| if (-1 < href.indexOf('|')) { | |
| const data = href.split('|'); | |
| href = data[0]; | |
| attrs = `target="${data[1]}"`; | |
| } | |
| //--- ~' ' : 새창에서 표시 | |
| if (text.endsWith(' ')) { | |
| text = text.trim(); | |
| attrs = 'target="_blank"'; | |
| } | |
| // http://local.bluestones.biz:90/cms/manual/service_cms_bluestone/default/ko_KR/${config.http.path}/invests | |
| href = href.replace(/ /g, '%20'); | |
| if (href.startsWith('http')) { | |
| return `<a href="${href}" target="_blank">${text}</a>`; | |
| } else if (href.startsWith('mailto:')) { | |
| return `<a href="${href}">${text}</a>`; | |
| } else if (href.startsWith('tel:')) { | |
| return `<a href="${href}">${text}</a>`; | |
| } else { | |
| if (href.endsWith('.md')) { | |
| if (href.startsWith('/')) { | |
| return `<a href="${utils.link.join(config.http.baseUrl, config.http.path, href)}" ${attrs}>${text}</a>`; | |
| } else { | |
| return `<a href="${utils.link.join(config.http.baseUrl, config.http.path, options.folder, href)}" ${attrs}>${text}</a>`; | |
| } | |
| } else if (href.startsWith('record=')) { | |
| return `<a href="${utils.link.join(config.http.baseUrl, config.http.path, '/markdowns?action=convert&', href)}" ${attrs}>${text}</a>`; | |
| } else { | |
| if (href.startsWith('/')) { | |
| return `<a href="${utils.link.join(config.http.path, href)}" ${attrs}>${text}</a>`; | |
| } else { | |
| return `<a href="${utils.link.join(options.baseUrl, options.folder, href)}" ${attrs}>${text}</a>`; | |
| } | |
| } | |
| } | |
| } | |
| //--- 이미지 이름에서 __ 뒤에 있는 문자열로 넓이를 계산 한다. | |
| //--- ~__width : style="width: ${width}" | |
| //--- ~__widthp : style="width: ${width}%" | |
| // 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};`; | |
| // } | |
| //--- 이미지 href 구성 : href | width | |
| // function _parseImageHref(href) { | |
| // const data = href.split('|'); | |
| // const myBaseUrl = config.http.baseUrl.substring(0, config.http.baseUrl.length - 1); | |
| // const src = myBaseUrl + options.baseUrl + ((href.startsWith('/')) ? '':`${options.folder}/`) + data[0]; | |
| // return { | |
| // src: (href.startsWith('http')) ? data[0]:src, | |
| // attrs: `style="width: ${data[1]}"` | |
| // }; | |
| // } | |
| // image(string href, string title, string text) | |
| renderer.image = function(href, title, text) { | |
| let attrs = ''; | |
| //--- 이미지 href 구성 : href | width | |
| if (-1 < href.indexOf('|')) { | |
| const data = href.split('|'); | |
| href = data[0]; | |
| attrs = `style="width: ${data[1]}"`; | |
| } | |
| //--- 이미지 이름에서 __ 뒤에 있는 문자열로 넓이를 계산 한다. | |
| //--- ~__width : style="width: ${width}" | |
| //--- ~__widthp : style="width: ${width}%" | |
| const basename = path.basename(href); | |
| if (-1 < basename.lastIndexOf('__')) { | |
| let width = (-1 < basename.indexOf('.')) ? basename.substring(0, basename.indexOf('.')):basename; | |
| width = width.substring(width.lastIndexOf('__') + 2); | |
| if (width.endsWith('p')) { | |
| width = width.substring(0, width.length - 1) + '%'; | |
| } | |
| attrs = `style="width: ${width}"`; | |
| } | |
| if (href.startsWith('http')) { | |
| return `<img src="${href}" alt="${text}" ${attrs}>`; | |
| } else { | |
| const myBaseUrl = (config.http.baseUrl.endsWith('/')) ? config.http.baseUrl.substring(0, config.http.baseUrl.length - 1):config.http.baseUrl; | |
| if (href.startsWith('/')) { | |
| return `<img src="${myBaseUrl}${options.baseUrl}${href}" alt="${text}" ${attrs}>`; | |
| } else { | |
| return `<img src="${myBaseUrl}${options.baseUrl}${options.folder}/${href}" alt="${text}" ${attrs}>`; | |
| } | |
| } | |
| // if (href.startsWith('http')) { | |
| // return `<img src="${href}" alt="${text}">`; | |
| // } else { | |
| // const width = _getWidth(href); | |
| // const 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}">`; | |
| // } | |
| // } | |
| } | |
| return renderer; | |
| } | |
| _getIndexParser(options) { | |
| const renderer = this._getNoneParser(); | |
| let headingIndex = 0; | |
| renderer.heading = function(text, level, raw, slugger) { | |
| headingIndex = headingIndex + 1; | |
| const pageIndex = { | |
| id: `heading_${headingIndex}`, | |
| text: text, | |
| level: level | |
| }; | |
| return JSON.stringify(pageIndex) + ','; | |
| } | |
| return renderer; | |
| } | |
| _getMenuParser(options) { | |
| const debug = this.debug.bind(this); | |
| const renderer = this._getNoneParser(); | |
| let idxAll = 0; | |
| let menuGroupIdx = 0; //--- 메뉴 그룹의 인덱스 | |
| let menuIdx = 0; //--- 메뉴의 인덱스 | |
| renderer.list = function(body, ordered, start) { | |
| idxAll = idxAll + 1; | |
| if (ordered) { | |
| //--- 현재 사용하지 않음 | |
| return `<ol start="${start}" class="idxAll_${idxAll}_${menuGroupIdx}_${menuIdx + 1}">${body}</ol>`; | |
| } else { | |
| menuGroupIdx = menuGroupIdx + 1; | |
| return `<ul class="nav flex-column nav-pills idxAll_${idxAll}_${menuGroupIdx}_${menuIdx + 1}" id="menuGroup_${menuGroupIdx}">${body}</ul>`; | |
| } | |
| } | |
| renderer.listitem = function(text, task, checked) { | |
| idxAll = idxAll + 1; | |
| text = text.replace(/<p>/g, '').replace(/<\/p>/g, ''); | |
| if (text.startsWith('<a ')) { | |
| return `<li class="nav-item idxAll_${idxAll}_${menuGroupIdx + 1}_${menuIdx}">${text}</li>`; | |
| } else { | |
| //--- 메뉴에는 되나 선택시 정상 동작하지 않음 | |
| return `<li class="nav-item idxAll_${idxAll}_${menuGroupIdx + 1}_${menuIdx}">${renderer.link('#', text, text)}</li>`; | |
| } | |
| } | |
| renderer.link = function(href, title, text) { | |
| idxAll = idxAll + 1; | |
| let txtParam = ''; | |
| let txtClass = `nav-link idxAll_${idxAll}_${menuGroupIdx + 1}_${menuIdx + 1}`; | |
| menuIdx = menuIdx + 1; | |
| if ((href.startsWith('record=')) && (options.file.endsWith(href + '.md'))) { | |
| txtClass = txtClass + ' active'; | |
| } else if (href == options.file) { | |
| txtClass = txtClass + ' active'; | |
| } | |
| txtParam = `class="${txtClass}" style="font-size: 14px;" id="menu_${menuIdx}"`; | |
| 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 if (href.startsWith('#')) { | |
| return `<a ${txtParam} onclick="selectMenu(this);" href="${href}">${text}</a>`; | |
| } else { | |
| //--- To-Do : href="url|_blank" 형태로 처리 | |
| let target = ''; | |
| if (text.endsWith(' ')) { | |
| target = 'target="_blank"'; | |
| text = text.trim(); | |
| } | |
| if (href.endsWith('.md')) { | |
| if (href.startsWith('/')) { | |
| return `<a ${txtParam} href="${utils.link.join(config.http.baseUrl, config.http.path, href)}" ${target}>${text}</a>`; | |
| } else { | |
| return `<a ${txtParam} href="${utils.link.join(config.http.baseUrl, config.http.path, options.folder, href)}" ${target}>${text}</a>`; | |
| } | |
| } else if (href.startsWith('record=')) { | |
| return `<a ${txtParam} href="${utils.link.join(config.http.baseUrl, config.http.path, '/markdowns?action=convert&', href)}" ${target}>${text}</a>`; | |
| } else { | |
| if (href.startsWith('/')) { | |
| return `<a ${txtParam} href="${utils.link.join(config.http.path, href)}" ${target}>${text}</a>`; | |
| } else { | |
| return `<a ${txtParam} href="${utils.link.join(options.baseUrl, options.folder, href)}" ${target}>${text}</a>`; | |
| } | |
| } | |
| } | |
| } | |
| let headingIndex = 0; | |
| renderer.heading = function(text, level, raw, slugger) { | |
| headingIndex = headingIndex + 1; | |
| const uniqueId = `heading_${headingIndex}`; | |
| if (level <= 2) { | |
| return `<br/><h${level} id="menu_${uniqueId}">${text}</h${level}><hr/>`; | |
| } else { | |
| return `<h${level} id="menu_${uniqueId}">${text}</h${level}>`; | |
| } | |
| } | |
| renderer.hr = () => '<hr/>'; | |
| renderer.paragraph = (text) => `<p>${text}</p>`; | |
| return renderer; | |
| } | |
| _getContentFromFile(filepath) { | |
| if ((filepath == null) || (fs.existsSync(filepath) == false)) { | |
| logger.error('File not found: ' + filepath); | |
| return ''; | |
| } | |
| return fs.readFileSync(filepath, "utf8"); | |
| } | |
| } | |
| module.exports = Markdown; | |