/** * amWiki Web端 - 文档加载与渲染模块 * @author Tevin */ ; (function (win, doc, $) { 'use strict'; var hljs = win.hljs; var marked = win.marked; var URL_ENCODE_NAME = 'AMWikiUrlEncode'; //记录url编码的键名 /** * 文档管理 * @constructor */ var Docs = function () { this.$e = { win: $(win), //markdown 文档内容容器 view: $('#view'), //网页 title 标签 title: $('title'), //目录悬浮窗标题 contentsTitle: $('#contentsTitle') }; this.data = { //记录页面宽度 pageWidth: 0 }; this._initUrlEncode(); this._initHashEvent(); }; /** * 初始化url编码类型标记 * @private */ Docs.prototype._initUrlEncode = function () { /* * 由于win与linux采用不同编码保存中文文件名 * 记录浏览器打开当前域名的wiki时服务器中文文件名的编码类型 */ if (!localStorage[URL_ENCODE_NAME]) { localStorage[URL_ENCODE_NAME] = 'utf8'; } else if (localStorage[URL_ENCODE_NAME] != 'utf8' && localStorage[URL_ENCODE_NAME] != 'gbk') { localStorage[URL_ENCODE_NAME] = 'utf8'; } //旧版本记录移除 delete localStorage.urlEcode }; /** * 修正移动端hash变化时滚动位置 * @private */ Docs.prototype._initHashEvent = function () { var that = this; this.data.pageWidth = this.$e.win.width(); this.$e.win.on('resize', function () { that.data.pageWidth = that.$e.win.width(); }); this.$e.win.on('hashchange', function (e) { var hash = location.hash.split('#')[1]; if (that.data.pageWidth <= 720) { that.$e.view.find('h1,h2,h3').each(function (index, element) { var $title = $(element); var text = $title.text().replace(/\s+/g, ''); if (hash == text) { that.$e.win.scrollTop($title.offset().top - 55); } }); } }); }; /** * 转换链接文本 * @param {String} str * @returns {String} * @private */ Docs.prototype._tramsformLinkText = function (str) { return str .replace(/^\s+|\s+$/g, '') //去除首尾空格 .replace(/'/g, ''') //转义单引号 .replace(/"/g, '"') //转义双引号,由于双引号无法正确传递给html属性,当作为hash时将删除处理 .replace(/\(/g, '(') //转义左圆括号 .replace(/\)/g, ')') //转义右圆括号 .replace(/\[/g, '[') //转义左中括号 .replace(/\]/g, ']'); //转义右中括号 }; /** * 设置文档h1、h2、h3描记 * @returns {string} * @private */ Docs.prototype._setTitlesAnchor = function () { var that = this; var $titles = null; var hash = ''; var contentsMd = ''; //提取目录为markdown字符串 if (location.hash && location.hash.length > 1) { hash = location.hash.split('#')[1]; } var anchorHtml = '' + ''; $titles = that.$e.view.find('h1,h2,h3'); $titles.each(function (index, element) { var $this = $(element); var text1 = that._tramsformLinkText($this.text()); var text2 = text1.replace(/"/g, ''); //删除双引号 //提取目录 if ($this.is('h2')) { contentsMd += '1. [' + text1 + '](#' + text2 + ' "' + text2 + '")\n'; } else if ($this.is('h3')) { contentsMd += '\t1. [' + text1 + '](#' + text2 + ' "' + text2 + '")\n'; } //设置描记 $this.prepend(anchorHtml.replace(/{title}/g, text2)); //首次打开页面滚动位置修正 if (hash == $this.text().replace(/"/g, '')) { if (that.data.pageWidth <= 720) { that.$e.win.scrollTop($this.offset().top - 55); } else { that.$e.win.scrollTop($this.offset().top); } } }); if (contentsMd.indexOf('\t') == 0) { contentsMd = '1.  \n' + contentsMd; } return contentsMd; }; /** * 创建脚注 * @param {String} text * @returns {String} * @private */ Docs.prototype._setFootnote = function (text) { var footnotes = []; var noteReg = /\[\^([ a-zA-Z\d]+)]/g; var footReg = /\[\^([ a-zA-Z\d]+)]: ?([\S\s]+?)(?=\[\^(?:[ a-zA-Z\d]+)]|\n\n|$)/g; var templates = $.trim($('#template\\:footnote').text()).split(/[\r\n]+\s*/g); templates[4] += templates[5] + templates[6] + templates[7] + templates[8]; var html = ''; //提取脚注内容 text = text.replace(footReg, function (match, s1, s2, index) { var title = ''; s2 = s2.replace(/"(.*?)"\s*$/, function (m, ss1) { title = ss1; return ''; }); footnotes.push({ index: index, note: s1, content: s2, title: title, used: false }); //从页面上删除底部脚注内容 return ''; }); //将脚注的标记转为序号 text = text.replace(noteReg, function (match, s1) { for (var i = 0, foot; foot = footnotes[i]; i++) { if (foot.note == s1) { foot.used = true; return templates[0].replace(/{{index}}/g, i + 1 + '').replace('{{title}}', foot.title); } } //当脚注的正文不存在,视标记文本为正文 var length = footnotes.push({ index: 0, note: s1, content: s1, used: true }); return templates[0].replace(/{{index}}/g, length + ''); }); //生成底部脚注html if (footnotes.length >= 1) { for (var i = 0, foot; foot = footnotes[i]; i++) { if (foot.used) { html += templates[2] .replace('{{index}}', i + 1) .replace('{{content}}', foot.content) .replace('{{back}}', templates[4].replace('{{index}}', i + 1)); } else { html += templates[3].replace('{{content}}', foot.content); } } html = templates[1].replace('{{list}}', html); } return text + '\n
' + html; }; /** * 设置js代码块注释显示隐藏 * @param {Object} $elm * @private */ Docs.prototype._setJSCommentDisable = function ($elm) { var $disBtn = $('
/
'); $disBtn.on('click', function () { var $this = $(this); if ($this.hasClass('on')) { $this.removeClass('on'); $elm.find('.hljs-comment').show(); } else { $this.addClass('on'); $elm.find('.hljs-comment').hide(); } }); $elm.prepend($disBtn); }; /** * 解析流程图 * @param {Object} $elm * @private */ Docs.prototype._createFlowChart = function ($elm) { var code = $elm.text(); $elm.text(''); var id = 'flowChart' + parseInt(Math.random() * 500); $elm.attr('id', id); var chart = flowchart.parse(code); chart.drawSVG(id, { 'line-width': 1.3, 'line-length': 56, 'line-color': '#666', 'text-margin': 10, 'font-size': 12, 'font': 'normal', 'font-family': 'Helvetica', 'font-weight': 'normal', 'font-color': 'black', 'element-color': '#888', 'fill': '#fff', 'yes-text': '是', 'no-text': '否', 'arrow-end': 'block-wide-long', 'symbols': {}, 'flowstate': {} }); }; /** * 解析 Markdown 目录 * @param {String} html * @returns {String} * @private */ Docs.prototype._setTOC = function (html) { return html.replace(/\[(TOC|MENU)]/g, '
'); }; /** * 解析 Markdown 自定义图片大小与对齐方式 * @param {String} html * @returns {String} * @private */ Docs.prototype._setImgResize = function (html) { return html.replace(/' : 'checked="true">'; checkboxHtml += ''; return checkboxHtml + s3; }); }; /** * 解析 markdown 文字飘红 * @param {String} html * @returns {String} * @private */ Docs.prototype._setRedText = function (html) { return html.replace(/==(.*?)==/g, function (m, s1) { return '' + s1 + ''; }); }; /** * 创建目录 * @param {String} contents - markdown 目录 * @private */ Docs.prototype._createContents = function (contents) { var $contents = $('.markdown-contents').html(marked(contents)); $('blockquote').each(function () { var $this = $(this); var $links = $this.find('ol>li>a'); //至少2条链接才视为目录 if ($links.length > 1) { $this.addClass('markdown-contents'); $contents = $contents.add($this); } }); //自带序号的目录,不再额外显示一层序号 $contents.find('ol').each(function () { var $this = $(this); var $links = $this.children('li').children('a'); var text1 = $links.eq(0).text(), text2 = $links.eq(1).text(); var conditions = [ //普通数字类型 /^[\((]?(\d+\.?)+[^\d]{2,}/, //汉字序号类型 /^[\((【第]?[一二三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+/, //英文序号类型 /^(chapter)|(section)|(part)|(step)/i, //罗马序号类型 /^[ⅠⅡⅢⅣIⅤⅥⅦⅧⅨⅩⅪⅫLCDM]+\.?/ ]; for (var i = 0, cond; cond = conditions[i]; i++) { if (cond.test(text1) && cond.test(text2)) { $this.addClass('unindex'); break; } } }); //没写序号的目录,创建序号 var setIndex = function ($elm, index1) { if ($elm.length == 0) { return; } $elm.children('li').each(function (index2) { var $this = $(this); if (!$elm.hasClass('unindex')) { var index = typeof index1 == 'number' ? (index1 + 1) + '.' + (index2 + 1): (index2 + 1) + '.'; $this.prepend('' + index + ''); } setIndex($this.children('ol'), index2); }); }; $contents.children('ol').each(function () { setIndex($(this)); }); }; /** * 编码 url * 由于服务器可能存在 GBK 或 UTF-8 两种编码,中文路径编码不对需要切换才能访问 * @param {String} path * @param {String} type - 是否需要编码类型反转, GBK、UTF-8切换 * @returns {String} * @private */ Docs.prototype._encodeUrl = function (path, type) { var url = 'library/'; if (typeof AWConfig.libraryPrefix == 'string' && AWConfig.libraryPrefix.length > 0) { url = AWConfig.libraryPrefix.replace(/\\/g, '/').replace(/\/?&/, '/'); } var paths = []; //正常编码 if (type == 'normal') { if (localStorage[URL_ENCODE_NAME] == 'utf8') { url += encodeURI(path); } else if (localStorage[URL_ENCODE_NAME] == 'gbk') { paths = path.split('/').map(function (path) { return GBK.encode(path); }); url += paths.join('/'); } } //反转编码 else if (type == 'reverse') { if (localStorage[URL_ENCODE_NAME] == 'utf8') { paths = path.split('/').map(function (path) { return GBK.encode(path); }); url += paths.join('/'); } else if (localStorage[URL_ENCODE_NAME] == 'gbk') { url += encodeURI(path); } } url += '.md?t=' + (new Date()).getTime(); return url; }; /** * 渲染文档 * @param {String} content - 需要渲染的文档内容 * @public */ Docs.prototype.renderDoc = function (content) { var that = this; var html = ''; this.cleanView(); //创建脚注 content = this._setFootnote(content); //编译 markdown html = marked(content); //创建目录标记,和悬浮窗格式统一 html = this._setTOC(html); //自定义图片大小与对齐方式 html = this._setImgResize(html); //复选框 html = this._setCheckbox(html); //文字飘红 html = this._setRedText(html); //填充到页面 this.$e.view.html(html); //功能化代码块 this.$e.view.find('pre code').each(function (i, element) { var $elm = $(element); var className = $elm.attr('class') || ''; //创建流程图 if (className.indexOf('lang-flow') >= 0) { that._createFlowChart($elm); } //创建语法高亮 else if (className.indexOf('lang') >= 0) { hljs.highlightBlock(element); } //创建js注释开关 className = $elm.attr('class') || ''; if (className.indexOf('javascript') >= 0) { that._setJSCommentDisable($elm); } }); //设置网页title var title = this.$e.view.find('h1').eq(0).text(); this.$e.title.text(title); this.$e.contentsTitle.text(title).attr('href', '#' + title.replace(/"/g, '')); //创建描点 var contents = this._setTitlesAnchor(); //创建目录 this._createContents(contents); }; /** * 读取文档 * @param {String} url * @param {Function} callback * @public */ Docs.prototype.getDoc = function (url, callback) { var that = this; var ajaxData = { type: 'get', url: url, dataType: 'text' }; $.ajax(ajaxData) .done(function (data) { //如果请求响应包含html,视为报错 if (/^\s*