diff --git a/app/controllers/NoteController.go b/app/controllers/NoteController.go index 88070d5..6e6acab 100644 --- a/app/controllers/NoteController.go +++ b/app/controllers/NoteController.go @@ -2,17 +2,20 @@ package controllers import ( "github.com/revel/revel" -// "encoding/json" - "gopkg.in/mgo.v2/bson" - . "github.com/leanote/leanote/app/lea" + // "encoding/json" "github.com/leanote/leanote/app/info" + . "github.com/leanote/leanote/app/lea" + "gopkg.in/mgo.v2/bson" + "os" + "os/exec" + "regexp" "strings" -// "time" -// "github.com/leanote/leanote/app/types" -// "io/ioutil" -// "fmt" -// "bytes" -// "os" + "time" + "fmt" + // "github.com/leanote/leanote/app/types" + // "io/ioutil" + // "bytes" + // "os" ) type Note struct { @@ -293,6 +296,165 @@ func (c Note) SearchNoteByTags(tags []string) revel.Result { return c.RenderJson(blogs) } +// 生成PDF +func (c Note) ToPdf(noteId, appKey string) revel.Result { + // 虽然传了cookie但是这里还是不能得到userId, 所以还是通过appKey来验证之 + appKeyTrue, _ := revel.Config.String("app.secret") + if appKeyTrue != appKey { + return c.RenderText("error") + } + note := noteService.GetNoteById(noteId) + if note.NoteId == "" { + return c.RenderText("error") + } + + noteUserId := note.UserId.Hex() + content := noteService.GetNoteContent(noteId, noteUserId) + userInfo := userService.GetUserInfo(noteUserId) + + //------------------ + // 将content的图片转换为base64 + contentStr := content.Content + + siteUrlPattern := configService.GetSiteUrl() + if strings.Contains(siteUrlPattern, "https") { + siteUrlPattern = strings.Replace(siteUrlPattern, "https", "https*", 1) + } else { + siteUrlPattern = strings.Replace(siteUrlPattern, "http", "https*", 1) + } + + regImage, _ := regexp.Compile(` 0 && note.Tags[0] != "" { + } else { + note.Tags = nil + } + c.RenderArgs["blog"] = note + c.RenderArgs["content"] = contentStr + c.RenderArgs["userInfo"] = userInfo + userBlog := blogService.GetUserBlog(noteUserId) + c.RenderArgs["userBlog"] = userBlog + + return c.RenderTemplate("file/pdf.html") +} + +// 导出成PDF +func (c Note) ExportPdf(noteId string) revel.Result { + re := info.NewRe() + userId := c.GetUserId() + note := noteService.GetNoteById(noteId) + if note.NoteId == "" { + re.Msg = "No Note" + return c.RenderText("error") + } + + noteUserId := note.UserId.Hex() + // 是否有权限 + if noteUserId != userId { + // 是否是有权限协作的 + if !note.IsBlog && !shareService.HasReadPerm(noteUserId, userId, noteId) { + re.Msg = "No Perm" + return c.RenderText("error") + } + } + + // path 判断是否需要重新生成之 + guid := NewGuid() + fileUrlPath := "files/" + Digest3(noteUserId) + "/" + noteUserId + "/" + Digest2(guid) + "/images/pdf" + dir := revel.BasePath + "/" + fileUrlPath + if !MkdirAll(dir) { + return c.RenderText("error, no dir") + } + filename := guid + ".pdf" + path := dir + "/" + filename + + // leanote.com的secret + appKey, _ := revel.Config.String("app.secretLeanote") + if appKey == "" { + appKey, _ = revel.Config.String("app.secret") + } + + // 生成之 + binPath := configService.GetGlobalStringConfig("exportPdfBinPath") + // 默认路径 + if binPath == "" { + binPath = "/usr/local/bin/wkhtmltopdf" + } + + url := configService.GetSiteUrl() + "/note/toPdf?noteId=" + noteId + "&appKey=" + appKey + // cc := binPath + " --no-stop-slow-scripts --javascript-delay 10000 \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\"" + // cc := binPath + " \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\"" + // 等待--window-status为done的状态 + // http://madalgo.au.dk/~jakobt/wkhtmltoxdoc/wkhtmltopdf_0.10.0_rc2-doc.html + // wkhtmltopdf参数大全 + var cc string + if note.IsMarkdown { + cc = binPath + " --window-status done \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\"" + } else { + cc = binPath + " \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\"" + } + + cmd := exec.Command("/bin/sh", "-c", cc) + _, err := cmd.Output() + if err != nil { + return c.RenderText("export pdf error. " + fmt.Sprintf("%v", err)) + } + file, err := os.Open(path) + if err != nil { + return c.RenderText("export pdf error. " + fmt.Sprintf("%v", err)) + } + // http://stackoverflow.com/questions/8588818/chrome-pdf-display-duplicate-headers-received-from-the-server + // filenameReturn = strings.Replace(filenameReturn, ",", "-", -1) + filenameReturn := note.Title + filenameReturn = FixFilename(filenameReturn) + if filenameReturn == "" { + filenameReturn = "Untitled.pdf" + } else { + filenameReturn += ".pdf" + } + return c.RenderBinary(file, filenameReturn, revel.Attachment, time.Now()) // revel.Attachment +} + // 设置/取消Blog; 置顶 func (c Note) SetNote2Blog(noteId string, isBlog, isTop bool) revel.Result { re := noteService.ToBlog(c.GetUserId(), noteId, isBlog, isTop) diff --git a/app/controllers/admin/AdminSettingController.go b/app/controllers/admin/AdminSettingController.go index abda4b9..7a68416 100644 --- a/app/controllers/admin/AdminSettingController.go +++ b/app/controllers/admin/AdminSettingController.go @@ -73,15 +73,9 @@ func (c AdminSetting) DoDemo(demoUsername, demoPassword string) revel.Result { return c.RenderJson(re) } -// ToImage -// 长微博的bin路径phantomJs -func (c AdminSetting) ToImage() revel.Result { - c.RenderArgs["toImageBinPath"] = configService.GetGlobalStringConfig("toImageBinPath") - return c.RenderTemplate("admin/setting/toImage.html"); -} -func (c AdminSetting) DoToImage(toImageBinPath string) revel.Result { +func (c AdminSetting) ExportPdf(path string) revel.Result { re := info.NewRe() - re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "toImageBinPath", toImageBinPath) + re.Ok = configService.UpdateGlobalStringConfig(c.GetUserId(), "exportPdfBinPath", path) return c.RenderJson(re) } diff --git a/app/controllers/api/ApiNoteController.go b/app/controllers/api/ApiNoteController.go index 3c8feea..be344fa 100644 --- a/app/controllers/api/ApiNoteController.go +++ b/app/controllers/api/ApiNoteController.go @@ -9,6 +9,8 @@ import ( "regexp" "strings" "time" + "os/exec" + "os" // "github.com/leanote/leanote/app/types" // "io/ioutil" // "fmt" @@ -580,3 +582,83 @@ func (c ApiNote) GetHistories(noteId string) revel.Result { return c.RenderJson(re) } */ + +// 0.2 新增 +// 导出成PDF +// test localhost:9000/api/note/exportPdf?noteId=554f07bf05fcd15fa9000000&token=562211dc99c37ba6a7000001 +func (c ApiNote) ExportPdf(noteId string) revel.Result { + re := info.NewApiRe() + userId := c.getUserId() + if noteId == "" { + re.Msg = "noteNotExists" + return c.RenderJson(re) + } + + note := noteService.GetNoteById(noteId) + if note.NoteId == "" { + re.Msg = "noteNotExists" + return c.RenderJson(re) + } + + noteUserId := note.UserId.Hex() + // 是否有权限 + if noteUserId != userId { + // 是否是有权限协作的 + if !note.IsBlog && !shareService.HasReadPerm(noteUserId, userId, noteId) { + re.Msg = "noteNotExists" + return c.RenderJson(re) + } + } + + // path 判断是否需要重新生成之 + guid := NewGuid() + fileUrlPath := "files/" + Digest3(noteUserId) + "/" + noteUserId + "/" + Digest2(guid) + "/images/pdf" + dir := revel.BasePath + "/" + fileUrlPath + if !MkdirAll(dir) { + re.Msg = "noDir" + return c.RenderJson(re) + } + filename := guid + ".pdf" + path := dir + "/" + filename + + appKey, _ := revel.Config.String("app.secretLeanote") + if appKey == "" { + appKey, _ = revel.Config.String("app.secret") + } + + // 生成之 + binPath := configService.GetGlobalStringConfig("exportPdfBinPath") + // 默认路径 + if binPath == "" { + binPath = "/usr/local/bin/wkhtmltopdf" + } + + url := configService.GetSiteUrl() + "/note/toPdf?noteId=" + noteId + "&appKey=" + appKey + var cc string + if(note.IsMarkdown) { + cc = binPath + " --window-status done \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\"" + } else { + cc = binPath + " \"" + url + "\" \"" + path + "\"" // \"" + cookieDomain + "\" \"" + cookieName + "\" \"" + cookieValue + "\"" + } + + cmd := exec.Command("/bin/sh", "-c", cc) + _, err := cmd.Output() + if err != nil { + re.Msg = "sysError" + return c.RenderJson(re) + } + file, err := os.Open(path) + if err != nil { + re.Msg = "sysError" + return c.RenderJson(re) + } + + filenameReturn := note.Title + filenameReturn = FixFilename(filenameReturn) + if filenameReturn == "" { + filenameReturn = "Untitled.pdf" + } else { + filenameReturn += ".pdf" + } + return c.RenderBinary(file, filenameReturn, revel.Attachment, time.Now()) // revel.Attachment +} diff --git a/app/controllers/init.go b/app/controllers/init.go index 08321f5..7fba55a 100644 --- a/app/controllers/init.go +++ b/app/controllers/init.go @@ -48,7 +48,7 @@ var commonUrl = map[string]map[string]bool{"Index": map[string]bool{"Index": tru "FindPasswordUpdate": true, "Suggestion": true, }, - "Note": map[string]bool{"ToImage": true}, + "Note": map[string]bool{"ToPdf": true}, "Blog": map[string]bool{"Index": true, "View": true, "AboutMe": true, diff --git a/app/service/FileService.go b/app/service/FileService.go index 48b6f3a..b8b1867 100644 --- a/app/service/FileService.go +++ b/app/service/FileService.go @@ -1,14 +1,18 @@ package service import ( + "encoding/base64" + "fmt" + "github.com/leanote/leanote/app/db" + "github.com/leanote/leanote/app/info" . "github.com/leanote/leanote/app/lea" "github.com/revel/revel" - "github.com/leanote/leanote/app/info" - "github.com/leanote/leanote/app/db" "gopkg.in/mgo.v2/bson" - "time" + "io/ioutil" + "net/http" "os" "strings" + "time" ) const DEFAULT_ALBUM_ID = "52d3e8ac99c37b7f0d000001" @@ -115,6 +119,53 @@ func (this *FileService) UpdateImage(userId, fileId, title string) bool { return db.UpdateByIdAndUserIdField(db.Files, fileId, userId, "Title", title) } +func (this *FileService) GetFileBase64(userId, fileId string) (str string, mine string) { + defer func() { // 必须要先声明defer,否则不能捕获到panic异常 + if err := recover(); err != nil { + fmt.Println(err) // 这里的err其实就是panic传入的内容,55 + } + }() + + path := this.GetFile(userId, fileId) + + if path == "" { + return "", "" + } + + path = revel.BasePath + "/" + strings.TrimLeft(path, "/") + + ff, err := ioutil.ReadFile(path) + if err != nil { + return "", "" + } + + e64 := base64.StdEncoding + maxEncLen := e64.EncodedLen(len(ff)) + encBuf := make([]byte, maxEncLen) + + e64.Encode(encBuf, ff) + + mime := http.DetectContentType(ff) + + str = string(encBuf) + return str, mime +} + +// 得到图片base64, 图片要在之前添加data:image/png;base64, +func (this *FileService) GetImageBase64(userId, fileId string) string { + + str, mime := this.GetFileBase64(userId, fileId) + if str == "" { + return "" + } + switch mime { + case "image/gif", "image/jpeg", "image/pjpeg", "image/png", "image/tiff": + return fmt.Sprintf("data:%s;base64,%s", mime, str) + default: + } + return "data:image/png;base64," + str +} + // 获取文件路径 // 要判断是否具有权限 // userId是否具有fileId的访问权限 diff --git a/app/views/admin/nav.html b/app/views/admin/nav.html index e2f2ffa..2b44c0a 100644 --- a/app/views/admin/nav.html +++ b/app/views/admin/nav.html @@ -13,7 +13,6 @@ -
  • @@ -157,7 +156,7 @@
  • - + Register Share Note @@ -171,6 +170,15 @@
  • + +
  • + + + Export PDF + + +
  • + diff --git a/app/views/admin/setting/export_pdf.html b/app/views/admin/setting/export_pdf.html new file mode 100644 index 0000000..0a27d13 --- /dev/null +++ b/app/views/admin/setting/export_pdf.html @@ -0,0 +1,52 @@ +{{template "admin/top.html" .}} +

    Export PDF

    + +
    + +
    +
    +
    +
    +
    + + + Leanote use wkhtmltopdf to export pdf. You should install it on your server. +
    + Please input the path that wkhtmltopdf installed, e.g. /usr/local/bin/wkhtmltopdf +
    +
    + +
    + +
    +
    +
    +
    + +
    + +{{template "admin/footer.html" .}} + + + +{{template "admin/end.html" .}} \ No newline at end of file diff --git a/app/views/admin/setting/shareNote.html b/app/views/admin/setting/share_note.html similarity index 100% rename from app/views/admin/setting/shareNote.html rename to app/views/admin/setting/share_note.html diff --git a/app/views/file/pdf.html b/app/views/file/pdf.html new file mode 100644 index 0000000..919ce0c --- /dev/null +++ b/app/views/file/pdf.html @@ -0,0 +1,124 @@ + + + + + + +{{.title}} + + + + + +
    +

    + {{if .blog.Title}} + {{.blog.Title}} + {{else}} + Untitled + {{end}} +

    +
    + + {{if .blog.Tags}} + + {{blogTagsForExport $ .blog.Tags}} + {{end}} +
    + +
    + {{if .blog.IsMarkdown }} + +
    +
    + {{else}} + {{.content | raw}} + {{end}} +
    +
    + + + + + + + + + + +{{if not .blog.IsMarkdown }} + +{{end}} + +{{if .blog.IsMarkdown }} + + +{{end}} + + \ No newline at end of file diff --git a/conf/routes b/conf/routes index aaea1a7..7a26c58 100644 --- a/conf/routes +++ b/conf/routes @@ -38,6 +38,8 @@ POST /findPasswordUpdate Auth.FindPasswordUpdate * /note/copySharedNote Note.CopySharedNote * /note/searchNoteByTags Note.SearchNoteByTags * /note/setNote2Blog Note.SetNote2Blog +* /note/exportPdf Note.ExportPDF +* /note/toPdf Note.ToPdf # pjax GET /note/:noteId Note.Index diff --git a/public/admin/css/admin.css b/public/admin/css/admin.css index 695d0b1..50a2f33 100644 --- a/public/admin/css/admin.css +++ b/public/admin/css/admin.css @@ -907,7 +907,7 @@ body { margin: 0; } a { - color: #2e3e4e; + color: #346EA9; text-decoration: none; } a:hover, diff --git a/public/admin/css/admin.less b/public/admin/css/admin.less index 04c9dca..8c4d863 100644 --- a/public/admin/css/admin.less +++ b/public/admin/css/admin.less @@ -1088,7 +1088,7 @@ body { } a { - color: #2e3e4e; + color: #346EA9; text-decoration: none; } diff --git a/public/css/toImage.css b/public/css/pdf.css similarity index 86% rename from public/css/toImage.css rename to public/css/pdf.css index 5cad1cf..92da892 100644 --- a/public/css/toImage.css +++ b/public/css/pdf.css @@ -1,7 +1,3 @@ -@font-face { - font-family: Si; - src: url("/public/fonts/MSYH.TTF"); -} @font-face { font-family: 'Open Sans'; font-style: normal; @@ -28,14 +24,14 @@ } *, body { - font-family: Si, 'Microsoft YaHei', 'WenQuanYi Micro Hei', 'Helvetica Neue', Arial, 'Hiragino Sans GB'; + font-family: 'Microsoft YaHei', 'WenQuanYi Micro Hei', 'Helvetica Neue', Arial, 'Hiragino Sans GB'; font-weight: 300; font-size: 14px; } h1, h2, h3 { - font-family: Si, 'Microsoft YaHei', 'WenQuanYi Micro Hei', 'Helvetica Neue', Arial, 'Hiragino Sans GB'; + font-family: 'Microsoft YaHei', 'WenQuanYi Micro Hei', 'Helvetica Neue', Arial, 'Hiragino Sans GB'; } * { font-size: 16px; diff --git a/public/css/toImage.less b/public/css/pdf.less similarity index 92% rename from public/css/toImage.less rename to public/css/pdf.less index 8104bba..7a6504d 100644 --- a/public/css/toImage.less +++ b/public/css/pdf.less @@ -1,8 +1,3 @@ -@font-face { - font-family: Si; - src:url("/public/fonts/MSYH.TTF"); -} - // font @font-face { font-family: 'Open Sans'; @@ -31,7 +26,7 @@ @bgColor: #fff; @headerBgColor: #fff; -@fontFamily: Si, 'Microsoft YaHei','WenQuanYi Micro Hei','Helvetica Neue',Arial,'Hiragino Sans GB'; +@fontFamily: 'Microsoft YaHei','WenQuanYi Micro Hei','Helvetica Neue',Arial,'Hiragino Sans GB'; @green: #65bd77; *, body { diff --git a/public/js/app/note.js b/public/js/app/note.js index b8f44b1..e50d56d 100644 --- a/public/js/app/note.js +++ b/public/js/app/note.js @@ -1096,6 +1096,12 @@ Note.download = function(url, params) { $('
    ' + inputs + '
    ').appendTo('body').submit().remove(); }; +// 导出成PDF +Note.exportPDF = function(target) { + var noteId = $(target).attr("noteId"); + $('
    ').appendTo('body').submit().remove(); +}; + //-------------- // read only @@ -1406,7 +1412,6 @@ Note.toggleReadOnly = function() { } note.readOnly = true; - Note.readOnly = true; }; // 切换到编辑模式 LEA.toggleWriteable = Note.toggleWriteable = function() { @@ -1505,6 +1510,9 @@ Note.initContextmenu = function() { { text: getMsg("publicAsBlog"), alias: 'set2Blog', faIcon: "fa-bold", action: Note.setNote2Blog }, { text: getMsg("cancelPublic"), alias: 'unset2Blog', faIcon: "fa-undo", action: Note.setNote2Blog }, { type: "splitLine" }, + // { text: "分享到社区", alias: 'html2Image', icon: "", action: Note.html2Image}, + { text: getMsg("exportPdf"), alias: 'exportPDF', faIcon: "fa-file-pdf-o", action: Note.exportPDF}, + { type: "splitLine" }, { text: getMsg("delete"), icon: "", faIcon: "fa-trash-o", action: Note.deleteNote }, { text: getMsg("move"), alias: "move", faIcon: "fa-arrow-right", type: "group", diff --git a/public/libs/md2html/md2html_for_export.js b/public/libs/md2html/md2html_for_export.js new file mode 100644 index 0000000..b7db7be --- /dev/null +++ b/public/libs/md2html/md2html_for_export.js @@ -0,0 +1,632 @@ +/** + * Markdown convert to html + * + + + * + * @author leanote.com + * @date 2015/04/11 + */ + +// Markdown.Converter.js +var Markdown;if(typeof exports==="object"&&typeof require==="function"){Markdown=exports}else{Markdown={}}(function(){function identity(x){return x}function returnFalse(x){return false}function HookCollection(){}HookCollection.prototype={chain:function(hookname,func){var original=this[hookname];if(!original){throw new Error("unknown hook "+hookname)}if(original===identity){this[hookname]=func}else{this[hookname]=function(text){var args=Array.prototype.slice.call(arguments,0);args[0]=original.apply(null,args);return func.apply(null,args)}}},set:function(hookname,func){if(!this[hookname]){throw new Error("unknown hook "+hookname)}this[hookname]=func},addNoop:function(hookname){this[hookname]=identity},addFalse:function(hookname){this[hookname]=returnFalse}};Markdown.HookCollection=HookCollection;function SaveHash(){}SaveHash.prototype={set:function(key,value){this["s_"+key]=value},get:function(key){return this["s_"+key]}};Markdown.Converter=function(){var options={};this.setOptions=function(optionsParam){options=optionsParam};var pluginHooks=this.hooks=new HookCollection();pluginHooks.addNoop("plainLinkText");pluginHooks.addNoop("preConversion");pluginHooks.addNoop("postNormalization");pluginHooks.addNoop("preBlockGamut");pluginHooks.addNoop("postBlockGamut");pluginHooks.addNoop("preSpanGamut");pluginHooks.addNoop("postSpanGamut");pluginHooks.addNoop("postConversion");var g_urls;var g_titles;var g_html_blocks;var g_list_level;this.makeHtml=function(text){if(g_urls){throw new Error("Recursive call to converter.makeHtml")}g_urls=new SaveHash();g_titles=new SaveHash();g_html_blocks=[];g_list_level=0;text=pluginHooks.preConversion(text);text=text.replace(/~/g,"~T");text=text.replace(/\$/g,"~D");text=text.replace(/\r\n/g,"\n");text=text.replace(/\r/g,"\n");text="\n\n"+text+"\n\n";text=_Detab(text);text=text.replace(/^[ \t]+$/mg,"");text=pluginHooks.postNormalization(text);text=_HashHTMLBlocks(text);text=_StripLinkDefinitions(text);text=_RunBlockGamut(text);text=_UnescapeSpecialChars(text);text=text.replace(/~D/g,"$$");text=text.replace(/~T/g,"~");text=pluginHooks.postConversion(text);g_html_blocks=g_titles=g_urls=null;return text};function _StripLinkDefinitions(text){text=text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*?(?=\s|$)[ \t]*\n?[ \t]*((\n*)["(](.+?)[")][ \t]*)?(?:\n+)/gm,function(wholeMatch,m1,m2,m3,m4,m5){m1=m1.toLowerCase();g_urls.set(m1,_EncodeAmpsAndAngles(m2));if(m4){return m3}else{if(m5){g_titles.set(m1,m5.replace(/"/g,"""))}}return""});return text}function _HashHTMLBlocks(text){var block_tags_a="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del";var block_tags_b="p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math";text=text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm,hashElement);text=text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math)\b[^\r]*?<\/\2>[ \t]*(?=\n+)\n)/gm,hashElement);text=text.replace(/\n[ ]{0,3}((<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g,hashElement);text=text.replace(/\n\n[ ]{0,3}(-]|-[^>])(?:[^-]|-[^-])*)--)>[ \t]*(?=\n{2,}))/g,hashElement);text=text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g,hashElement);return text}function hashElement(wholeMatch,m1){var blockText=m1;blockText=blockText.replace(/^\n+/,"");blockText=blockText.replace(/\n+$/g,"");blockText="\n\n~K"+(g_html_blocks.push(blockText)-1)+"K\n\n";return blockText}var blockGamutHookCallback=function(t){return _RunBlockGamut(t)};function _RunBlockGamut(text,doNotUnhash){text=pluginHooks.preBlockGamut(text,blockGamutHookCallback);text=_DoHeaders(text);var replacement="
    \n";text=text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm,replacement);text=text.replace(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/gm,replacement);text=text.replace(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/gm,replacement);text=_DoLists(text);text=_DoCodeBlocks(text);text=_DoBlockQuotes(text);text=pluginHooks.postBlockGamut(text,blockGamutHookCallback);text=_HashHTMLBlocks(text);text=_FormParagraphs(text,doNotUnhash);return text}function _RunSpanGamut(text){text=pluginHooks.preSpanGamut(text);text=_DoCodeSpans(text);text=_EscapeSpecialCharsWithinTagAttributes(text);text=_EncodeBackslashEscapes(text);text=_DoImages(text);text=_DoAnchors(text);text=_DoAutoLinks(text);text=text.replace(/~P/g,"://");text=_EncodeAmpsAndAngles(text);text=options._DoItalicsAndBold?options._DoItalicsAndBold(text):_DoItalicsAndBold(text);text=text.replace(/ +\n/g,"
    \n");text=pluginHooks.postSpanGamut(text);return text}function _EscapeSpecialCharsWithinTagAttributes(text){var regex=/(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|-]|-[^>])(?:[^-]|-[^-])*)--)>)/gi;text=text.replace(regex,function(wholeMatch){var tag=wholeMatch.replace(/(.)<\/?code>(?=.)/g,"$1`");tag=escapeCharacters(tag,wholeMatch.charAt(1)=="!"?"\\`*_/":"\\`*_"); +return tag});return text}function _DoAnchors(text){text=text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeAnchorTag);text=text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeAnchorTag);text=text.replace(/(\[([^\[\]]+)\])()()()()()/g,writeAnchorTag);return text}function writeAnchorTag(wholeMatch,m1,m2,m3,m4,m5,m6,m7){if(m7==undefined){m7=""}var whole_match=m1;var link_text=m2.replace(/:\/\//g,"~P");var link_id=m3.toLowerCase();var url=m4;var title=m7;if(url==""){if(link_id==""){link_id=link_text.toLowerCase().replace(/ ?\n/g," ")}url="#"+link_id;if(g_urls.get(link_id)!=undefined){url=g_urls.get(link_id);if(g_titles.get(link_id)!=undefined){title=g_titles.get(link_id)}}else{if(whole_match.search(/\(\s*\)$/m)>-1){url=""}else{return whole_match}}}url=encodeProblemUrlChars(url);url=escapeCharacters(url,"*_");var result='";return result}function _DoImages(text){text=text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g,writeImageTag);text=text.replace(/(!\[(.*?)\]\s?\([ \t]*()?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g,writeImageTag);return text}function attributeEncode(text){return text.replace(/>/g,">").replace(/"+_RunSpanGamut(m1)+"\n\n"});text=text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,function(matchFound,m1){return"

    "+_RunSpanGamut(m1)+"

    \n\n"});text=text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,function(wholeMatch,m1,m2){var h_level=m1.length;return""+_RunSpanGamut(m2)+"\n\n"});return text}function _DoLists(text,isInsideParagraphlessListItem){text+="~0";var whole_list=/^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;if(g_list_level){text=text.replace(whole_list,function(wholeMatch,m1,m2){var list=m1;var list_type=(m2.search(/[*+-]/g)>-1)?"ul":"ol";var result=_ProcessListItems(list,list_type,isInsideParagraphlessListItem);result=result.replace(/\s+$/,"");result="<"+list_type+">"+result+"\n";return result})}else{whole_list=/(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;text=text.replace(whole_list,function(wholeMatch,m1,m2,m3){var runup=m1;var list=m2;var list_type=(m3.search(/[*+-]/g)>-1)?"ul":"ol";var result=_ProcessListItems(list,list_type);result=runup+"<"+list_type+">\n"+result+"\n";return result})}text=text.replace(/~0/,"");return text}var _listItemMarkers={ol:"\\d+[.]",ul:"[*+-]"};function _ProcessListItems(list_str,list_type,isInsideParagraphlessListItem){g_list_level++;list_str=list_str.replace(/\n{2,}$/,"\n");list_str+="~0";var marker=_listItemMarkers[list_type];var re=new RegExp("(^[ \\t]*)("+marker+")[ \\t]+([^\\r]+?(\\n+))(?=(~0|\\1("+marker+")[ \\t]+))","gm");var last_item_had_a_double_newline=false;list_str=list_str.replace(re,function(wholeMatch,m1,m2,m3){var item=m3;var leading_space=m1;var ends_with_double_newline=/\n\n$/.test(item);var contains_double_newline=ends_with_double_newline||item.search(/\n{2,}/)>-1;if(contains_double_newline||last_item_had_a_double_newline){item=_RunBlockGamut(_Outdent(item),true)}else{item=_DoLists(_Outdent(item),true);item=item.replace(/\n$/,"");if(!isInsideParagraphlessListItem){item=_RunSpanGamut(item)}}last_item_had_a_double_newline=ends_with_double_newline;return"
  • "+item+"
  • \n"});list_str=list_str.replace(/~0/g,"");g_list_level--;return list_str}function _DoCodeBlocks(text){text+="~0";text=text.replace(/(?:\n\n|^\n?)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,function(wholeMatch,m1,m2){var codeblock=m1;var nextChar=m2;codeblock=_EncodeCode(_Outdent(codeblock));codeblock=_Detab(codeblock);codeblock=codeblock.replace(/^\n+/g,"");codeblock=codeblock.replace(/\n+$/g,"");codeblock="
    "+codeblock+"\n
    ";return"\n\n"+codeblock+"\n\n"+nextChar});text=text.replace(/~0/,"");return text}function hashBlock(text){text=text.replace(/(^\n+|\n+$)/g,"");return"\n\n~K"+(g_html_blocks.push(text)-1)+"K\n\n" +}function _DoCodeSpans(text){text=text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,function(wholeMatch,m1,m2,m3,m4){var c=m3;c=c.replace(/^([ \t]*)/g,"");c=c.replace(/[ \t]*$/g,"");c=_EncodeCode(c);c=c.replace(/:\/\//g,"~P");return m1+""+c+""});return text}function _EncodeCode(text){text=text.replace(/&/g,"&");text=text.replace(//g,">");text=escapeCharacters(text,"*_{}[]\\",false);return text}function _DoItalicsAndBold(text){text=text.replace(/([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/g,"$1$3$4");text=text.replace(/([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/g,"$1$3$4");return text}function _DoBlockQuotes(text){text=text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,function(wholeMatch,m1){var bq=m1;bq=bq.replace(/^[ \t]*>[ \t]?/gm,"~0");bq=bq.replace(/~0/g,"");bq=bq.replace(/^[ \t]+$/gm,"");bq=_RunBlockGamut(bq);bq=bq.replace(/(^|\n)/g,"$1 ");bq=bq.replace(/(\s*
    [^\r]+?<\/pre>)/gm,function(wholeMatch,m1){var pre=m1;pre=pre.replace(/^  /mg,"~0");pre=pre.replace(/~0/g,"");return pre});return hashBlock("
    \n"+bq+"\n
    ")});return text}function _FormParagraphs(text,doNotUnhash){text=text.replace(/^\n+/g,"");text=text.replace(/\n+$/g,"");var grafs=text.split(/\n{2,}/g);var grafsOut=[];var markerRe=/~K(\d+)K/;var end=grafs.length;for(var i=0;i");str+="

    ";grafsOut.push(str)}}}if(!doNotUnhash){end=grafsOut.length;for(var i=0;i#+-.!])/g,escapeCharacters_callback);return text}var charInsideUrl="[-A-Z0-9+&@#/%?=~_|[\\]()!:,.;]",charEndingUrl="[-A-Z0-9+&@#/%=~_|[\\])]",autoLinkRegex=new RegExp('(="|<)?\\b(https?|ftp)(://'+charInsideUrl+"*"+charEndingUrl+")(?=$|\\W)","gi"),endCharRegex=new RegExp(charEndingUrl,"i");function handleTrailingParens(wholeMatch,lookbehind,protocol,link){if(lookbehind){return wholeMatch}if(link.charAt(link.length-1)!==")"){return"<"+protocol+link+">"}var parens=link.match(/[()]/g);var level=0;for(var i=0;i"+tail}function _DoAutoLinks(text){text=text.replace(autoLinkRegex,handleTrailingParens);var replacer=function(wholematch,m1){return'
    '+pluginHooks.plainLinkText(m1)+""};text=text.replace(/<((https?|ftp):[^'">\s]+)>/gi,replacer);return text}function _UnescapeSpecialChars(text){text=text.replace(/~E(\d+)E/g,function(wholeMatch,m1){var charCodeToReplace=parseInt(m1);return String.fromCharCode(charCodeToReplace)});return text}function _Outdent(text){text=text.replace(/^(\t|[ ]{1,4})/gm,"~0");text=text.replace(/~0/g,"");return text}function _Detab(text){if(!/\t/.test(text)){return text}var spaces=[" "," "," "," "],skew=0,v;return text.replace(/[\n\t]/g,function(match,offset){if(match==="\n"){skew=offset+1;return match}v=(offset-skew)%4;skew=offset+1;return spaces[v]})}var _problemUrlChars=/(?:["'*()[\]:]|~D)/g;function encodeProblemUrlChars(url){if(!url){return""}var len=url.length;return url.replace(_problemUrlChars,function(match,offset){if(match=="~D"){return"%24"}if(match==":"){return":"}return"%"+match.charCodeAt(0).toString(16)})}function escapeCharacters(text,charsToEscape,afterBackslash){var regexString="(["+charsToEscape.replace(/([\[\]\\])/g,"\\$1")+"])";if(afterBackslash){regexString="\\\\"+regexString}var regex=new RegExp(regexString,"g");text=text.replace(regex,escapeCharacters_callback);return text}function escapeCharacters_callback(wholeMatch,m1){var charCodeToEscape=m1.charCodeAt(0);return"~E"+charCodeToEscape+"E"}}})(); + +// Markdown.Extra.js +(function(){var inlineTags=new RegExp(["^(<\\/?(a|abbr|acronym|applet|area|b|basefont|","bdo|big|button|cite|code|del|dfn|em|figcaption|","font|i|iframe|img|input|ins|kbd|label|map|","mark|meter|object|param|progress|q|ruby|rp|rt|s|","samp|script|select|small|span|strike|strong|","sub|sup|textarea|time|tt|u|var|wbr)[^>]*>|","<(br)\\s?\\/?>)$"].join(""),"i");if(!Array.indexOf){Array.prototype.indexOf=function(obj){for(var i=0;i]*>?/gi,function(tag){return tag.match(whitelist)?tag:""})}function union(x,y){var obj={};for(var i=0;i~X"+(this.hashBlocks.push(block)-1)+"X

    \n"};Markdown.Extra.prototype.hashExtraInline=function(block){return"~X"+(this.hashBlocks.push(block)-1)+"X"};Markdown.Extra.prototype.unHashExtraBlocks=function(text){var self=this;function recursiveUnHash(){var hasHash=false;text=text.replace(/(?:

    )?~X(\d+)X(?:<\/p>)?/g,function(wholeMatch,m1){hasHash=true;var key=parseInt(m1,10);return self.hashBlocks[key]});if(hasHash===true){recursiveUnHash()}}recursiveUnHash();return text};Markdown.Extra.prototype.wrapHeaders=function(text){function wrap(text){return"\n"+text+"\n"}text=text.replace(/^.+[ \t]*\n=+[ \t]*\n+/gm,wrap);text=text.replace(/^.+[ \t]*\n-+[ \t]*\n+/gm,wrap);text=text.replace(/^\#{1,6}[ \t]*.+?[ \t]*\#*\n+/gm,wrap);return text};var attrBlock="\\{[ \\t]*((?:[#.][-_:a-zA-Z0-9]+[ \\t]*)+)\\}";var hdrAttributesA=new RegExp("^(#{1,6}.*#{0,6})[ \\t]+"+attrBlock+"[ \\t]*(?:\\n|0x03)","gm");var hdrAttributesB=new RegExp("^(.*)[ \\t]+"+attrBlock+"[ \\t]*\\n"+"(?=[\\-|=]+\\s*(?:\\n|0x03))","gm");var fcbAttributes=new RegExp("^(```[^`\\n]*)[ \\t]+"+attrBlock+"[ \\t]*\\n"+"(?=([\\s\\S]*?)\\n```[ \\t]*(\\n|0x03))","gm");Markdown.Extra.prototype.hashHeaderAttributeBlocks=function(text){var self=this;function attributeCallback(wholeMatch,pre,attr){return"

    ~XX"+(self.hashBlocks.push(attr)-1)+"XX

    \n"+pre+"\n"}text=text.replace(hdrAttributesA,attributeCallback);text=text.replace(hdrAttributesB,attributeCallback);return text};Markdown.Extra.prototype.hashFcbAttributeBlocks=function(text){var self=this;function attributeCallback(wholeMatch,pre,attr){return"

    ~XX"+(self.hashBlocks.push(attr)-1)+"XX

    \n"+pre+"\n"}return text.replace(fcbAttributes,attributeCallback)};Markdown.Extra.prototype.applyAttributeBlocks=function(text){var self=this;var blockRe=new RegExp("

    ~XX(\\d+)XX

    [\\s]*"+'(?:<(h[1-6]|pre)(?: +class="(\\S+)")?(>[\\s\\S]*?))',"gm");text=text.replace(blockRe,function(wholeMatch,k,tag,cls,rest){if(!tag){return""}var key=parseInt(k,10);var attributes=self.hashBlocks[key];var id=attributes.match(/#[^\s#.]+/g)||[];var idStr=id[0]?' id="'+id[0].substr(1,id[0].length-1)+'"':"";var classes=attributes.match(/\.[^\s#.]+/g)||[];for(var i=0;i0){classStr=' class="'+classes.join(" ")+'"'}return"<"+tag+idStr+classStr+rest});return text};Markdown.Extra.prototype.tables=function(text){var self=this;var leadingPipe=new RegExp(["^","[ ]{0,3}","[|]","(.+)\\n","[ ]{0,3}","[|]([ ]*[-:]+[-| :]*)\\n","(","(?:[ ]*[|].*\\n?)*",")","(?:\\n|$)"].join(""),"gm");var noLeadingPipe=new RegExp(["^","[ ]{0,3}","(\\S.*[|].*)\\n","[ ]{0,3}","([-:]+[ ]*[|][-| :]*)\\n","(","(?:.*[|].*\\n?)*",")","(?:\\n|$)"].join(""),"gm");text=text.replace(leadingPipe,doTable);text=text.replace(noLeadingPipe,doTable);function doTable(match,header,separator,body,offset,string){header=header.replace(/^ *[|]/m,"");separator=separator.replace(/^ *[|]/m,"");body=body.replace(/^ *[|]/gm,"");header=header.replace(/[|] *$/m,"");separator=separator.replace(/[|] *$/m,"");body=body.replace(/[|] *$/gm,"");alignspecs=separator.split(/ *[|] */);align=[];for(var i=0;i\n","\n","\n"].join("");for(i=0;i",headerHtml,"\n"].join("")}html+="\n\n";var rows=body.split("\n");for(i=0;i\n";for(j=0;j",colHtml,"\n"].join("")}html+="\n"}html+="\n";return self.hashExtraBlock(html)}return text};Markdown.Extra.prototype.stripFootnoteDefinitions=function(text){var self=this;text=text.replace(/\n[ ]{0,3}\[\^(.+?)\]\:[ \t]*\n?([\s\S]*?)\n{1,2}((?=\n[ ]{0,3}\S)|$)/g,function(wholeMatch,m1,m2){m1=slugify(m1);m2+="\n";m2=m2.replace(/^[ ]{0,3}/g,"");self.footnotes[m1]=m2;return"\n"});return text};Markdown.Extra.prototype.doFootnotes=function(text){var self=this;if(self.isConvertingFootnote===true){return text}var footnoteCounter=0;text=text.replace(/\[\^(.+?)\]/g,function(wholeMatch,m1){var id=slugify(m1);var footnote=self.footnotes[id]; +if(footnote===undefined){return wholeMatch}footnoteCounter++;self.usedFootnotes.push(id);var html=''+footnoteCounter+"";return self.hashExtraInline(html)});return text};Markdown.Extra.prototype.printFootnotes=function(text){var self=this;if(self.usedFootnotes.length===0){return text}text+='\n\n
    \n
    \n
      \n\n';for(var i=0;i'+formattedfootnote+' \n\n'}text+="
    \n
    ";return text};Markdown.Extra.prototype.fencedCodeBlocks=function(text){function encodeCode(code){code=code.replace(/&/g,"&");code=code.replace(//g,">");code=code.replace(/~D/g,"$$");code=code.replace(/~T/g,"~");return code}var self=this;text=text.replace(/(?:^|\n)```([^`\n]*)\n([\s\S]*?)\n```[ \t]*(?=\n)/g,function(match,m1,m2){var language=trim(m1),codeblock=m2;var preclass=self.googleCodePrettify?' class="prettyprint"':"";var codeclass="";if(language){if(self.googleCodePrettify||self.highlightJs){codeclass=' class="language-'+language+'"'}else{codeclass=' class="'+language+'"'}}var html=["",encodeCode(codeblock),"
    "].join("");return self.hashExtraBlock(html)});return text};Markdown.Extra.prototype.educatePants=function(text){var self=this;var result="";var blockOffset=0;text.replace(/(?:)|(<)([a-zA-Z1-6]+)([^\n]*?>)([\s\S]*?)(<\/\2>)/g,function(wholeMatch,m1,m2,m3,m4,m5,offset){var token=text.substring(blockOffset,offset);result+=self.applyPants(token);self.smartyPantsLastChar=result.substring(result.length-1);blockOffset=offset+wholeMatch.length;if(!m1){result+=wholeMatch;return}if(!/code|kbd|pre|script|noscript|iframe|math|ins|del|pre/i.test(m2)){m4=self.educatePants(m4)}else{self.smartyPantsLastChar=m4.substring(m4.length-1)}result+=m1+m2+m3+m4+m5});var lastToken=text.substring(blockOffset);result+=self.applyPants(lastToken);self.smartyPantsLastChar=result.substring(result.length-1);return result};function revertPants(wholeMatch,m1){var blockText=m1;blockText=blockText.replace(/&\#8220;/g,'"');blockText=blockText.replace(/&\#8221;/g,'"');blockText=blockText.replace(/&\#8216;/g,"'");blockText=blockText.replace(/&\#8217;/g,"'");blockText=blockText.replace(/&\#8212;/g,"---");blockText=blockText.replace(/&\#8211;/g,"--");blockText=blockText.replace(/&\#8230;/g,"...");return blockText}Markdown.Extra.prototype.applyPants=function(text){text=text.replace(/---/g,"—").replace(/--/g,"–");text=text.replace(/\.\.\./g,"…").replace(/\.\s\.\s\./g,"…");text=text.replace(/``/g,"“").replace(/''/g,"”");if(/^'$/.test(text)){if(/\S/.test(this.smartyPantsLastChar)){return"’"}return"‘"}if(/^"$/.test(text)){if(/\S/.test(this.smartyPantsLastChar)){return"”"}return"“"}text=text.replace(/^'(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/,"’");text=text.replace(/^"(?=[!"#\$\%'()*+,\-.\/:;<=>?\@\[\\]\^_`{|}~]\B)/,"”");text=text.replace(/"'(?=\w)/g,"“‘");text=text.replace(/'"(?=\w)/g,"‘“");text=text.replace(/'(?=\d{2}s)/g,"’");text=text.replace(/(\s| |--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)'(?=\w)/g,"$1‘");text=text.replace(/([^\s\[\{\(\-])'/g,"$1’");text=text.replace(/'(?=\s|s\b)/g,"’");text=text.replace(/'/g,"‘");text=text.replace(/(\s| |--|&[mn]dash;|&\#8211;|&\#8212;|&\#x201[34];)"(?=\w)/g,"$1“");text=text.replace(/([^\s\[\{\(\-])"/g,"$1”");text=text.replace(/"(?=\s)/g,"”");text=text.replace(/"/ig,"“");return text};Markdown.Extra.prototype.runSmartyPants=function(text){this.smartyPantsLastChar="";text=this.educatePants(text);text=text.replace(/(<([a-zA-Z1-6]+)\b([^\n>]*?)(\/)?>)/g,revertPants);return text};Markdown.Extra.prototype.definitionLists=function(text){var wholeList=new RegExp(["(\\x02\\n?|\\n\\n)","(?:","(","(","[ ]{0,3}","((?:[ \\t]*\\S.*\\n)+)","\\n?","[ ]{0,3}:[ ]+",")","([\\s\\S]+?)","(","(?=\\0x03)","|","(?=","\\n{2,}","(?=\\S)","(?!","[ ]{0,3}","(?:\\S.*\\n)+?","\\n?","[ ]{0,3}:[ ]+",")","(?!","[ ]{0,3}:[ ]+",")",")",")",")",")"].join(""),"gm");var self=this;text=addAnchors(text);text=text.replace(wholeList,function(match,pre,list){var result=trim(self.processDefListItems(list));result="
    \n"+result+"\n
    ";return pre+self.hashExtraBlock(result)+"\n\n"});return removeAnchors(text)};Markdown.Extra.prototype.processDefListItems=function(listStr){var self=this;var dt=new RegExp(["(\\x02\\n?|\\n\\n+)","(","[ ]{0,3}","(?![:][ ]|[ ])","(?:\\S.*\\n)+?",")","(?=\\n?[ ]{0,3}:[ ])"].join(""),"gm");var dd=new RegExp(["\\n(\\n+)?","(","[ ]{0,3}","[:][ ]+",")","([\\s\\S]+?)","(?=\\n*","(?:","\\n[ ]{0,3}[:][ ]|","
    |\\x03",")",")"].join(""),"gm"); +listStr=addAnchors(listStr);listStr=listStr.replace(/\n{2,}(?=\\x03)/,"\n");listStr=listStr.replace(dt,function(match,pre,termsStr){var terms=trim(termsStr).split("\n");var text="";for(var i=0;i"+term+"
    "}return text+"\n"});listStr=listStr.replace(dd,function(match,leadingLine,markerSpace,def){if(leadingLine||def.match(/\n{2,}/)){def=Array(markerSpace.length+1).join(" ")+def;def=outdent(def)+"\n\n";def="\n"+convertAll(def,self)+"\n"}else{def=rtrim(def);def=convertSpans(outdent(def),self)}return"\n
    "+def+"
    \n"});return removeAnchors(listStr)};Markdown.Extra.prototype.strikethrough=function(text){return text.replace(/([\W_]|^)~T~T(?=\S)([^\r]*?\S[\*_]*)~T~T([\W_]|$)/g,"$1$2$3")};Markdown.Extra.prototype.newlines=function(text){return text.replace(/(<(?:br|\/li)>)?\n/g,function(wholeMatch,previousTag){return previousTag?wholeMatch:"
    \n"})}})(); + + +(function() { + + // Create the converter and the editor + var converter = new Markdown.Converter(); + var options = { + _DoItalicsAndBold: function(text) { + // Restore original markdown implementation + text = text.replace(/(\*\*|__)(?=\S)(.+?[*_]*)(?=\S)\1/g, + "$2"); + text = text.replace(/(\*|_)(?=\S)(.+?)(?=\S)\1/g, + "$2"); + return text; + } + }; + converter.setOptions(options); + + function loadJs(src, callback) { + var _doc = document.getElementsByTagName('head')[0]; + var script = document.createElement('script'); + script.setAttribute('type', 'text/javascript'); + script.setAttribute('src', src); + _doc.appendChild(script); + script.onload = script.onreadystatechange = function() { + if(!this.readyState || this.readyState=='loaded' || this.readyState=='complete'){ + callback && callback(); + } + script.onload = script.onreadystatechange = null; + } + } + + function _each(list, callback) { + if(list && list.length > 0) { + for(var i = 0; i < list.length; i++) { + callback(list[i]); + } + } + } + function _has(obj, key) { + return hasOwnProperty.call(obj, key); + }; + + // markdown extra + function initMarkdownExtra() { + // Create the converter and the editor + // var converter = new Markdown.Converter(); + var options = { + _DoItalicsAndBold: function(text) { + // Restore original markdown implementation + text = text.replace(/(\*\*|__)(?=\S)(.+?[*_]*)(?=\S)\1/g, + "$2"); + text = text.replace(/(\*|_)(?=\S)(.+?)(?=\S)\1/g, + "$2"); + return text; + } + }; + converter.setOptions(options); + + //================ + // markdown exstra + + var markdownExtra = {}; + markdownExtra.config = { + extensions: [ + "fenced_code_gfm", + "tables", + "def_list", + "attr_list", + "footnotes", + "smartypants", + "strikethrough", + "newlines" + ], + intraword: true, + comments: true, + highlighter: "highlight" + }; + var extraOptions = { + extensions: markdownExtra.config.extensions, + highlighter: "prettify" + }; + + if(markdownExtra.config.intraword === true) { + var converterOptions = { + _DoItalicsAndBold: function(text) { + text = text.replace(/([^\w*]|^)(\*\*|__)(?=\S)(.+?[*_]*)(?=\S)\2(?=[^\w*]|$)/g, "$1$3"); + text = text.replace(/([^\w*]|^)(\*|_)(?=\S)(.+?)(?=\S)\2(?=[^\w*]|$)/g, "$1$3"); + // Redo bold to handle _**word**_ + text = text.replace(/([^\w*]|^)(\*\*|__)(?=\S)(.+?[*_]*)(?=\S)\2(?=[^\w*]|$)/g, "$1$3"); + return text; + } + }; + converter.setOptions(converterOptions); + } + + if(markdownExtra.config.comments === true) { + converter.hooks.chain("postConversion", function(text) { + return text.replace(//g, function(wholeMatch) { + return wholeMatch.replace(/^$/, ' $1 '); + }); + }); + } + + Markdown.Extra.init(converter, extraOptions); + } + + //============== + // toc start + + function initToc() { + var toc = {}; + toc.config = { + marker: "\\[(TOC|toc)\\]", + maxDepth: 6, + button: true, + }; + + // TOC element description + function TocElement(tagName, anchor, text) { + this.tagName = tagName; + this.anchor = anchor; + this.text = text; + this.children = []; + } + TocElement.prototype.childrenToString = function() { + if(this.children.length === 0) { + return ""; + } + var result = "\n"; + return result; + }; + TocElement.prototype.toString = function() { + var result = "
  • "; + if(this.anchor && this.text) { + result += '' + this.text + ''; + } + result += this.childrenToString() + "
  • \n"; + return result; + }; + + // Transform flat list of TocElement into a tree + function groupTags(array, level) { + level = level || 1; + var tagName = "H" + level; + var result = []; + + var currentElement; + function pushCurrentElement() { + if(currentElement !== undefined) { + if(currentElement.children.length > 0) { + currentElement.children = groupTags(currentElement.children, level + 1); + } + result.push(currentElement); + } + } + + _each(array, function(element) { + if(element.tagName != tagName) { + if(level !== toc.config.maxDepth) { + if(currentElement === undefined) { + currentElement = new TocElement(); + } + currentElement.children.push(element); + } + } + else { + pushCurrentElement(); + currentElement = element; + } + }); + pushCurrentElement(); + return result; + } + + var utils = {}; + var nonWordChars = new RegExp('[^\\p{L}\\p{N}-]', 'g'); + utils.slugify = function(text) { + return text.toLowerCase().replace(/\s/g, '-') // Replace spaces with - + .replace(nonWordChars, '') // Remove all non-word chars + .replace(/\-\-+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, ''); // Trim - from end of text + }; + + // Build the TOC + var previewContentsElt; + function buildToc(previewContentsElt) { + var anchorList = {}; + function createAnchor(element) { + var id = element.id || utils.slugify(element.textContent) || 'title'; + var anchor = id; + var index = 0; + while (_has(anchorList, anchor)) { + anchor = id + "-" + (++index); + } + anchorList[anchor] = true; + // Update the id of the element + element.id = anchor; + return anchor; + } + + var elementList = []; + _each(previewContentsElt.querySelectorAll('h1, h2, h3, h4, h5, h6'), function(elt) { + elementList.push(new TocElement(elt.tagName, createAnchor(elt), elt.textContent)); + }); + elementList = groupTags(elementList); + return '
    \n\n
    \n'; + } + + toc.convert = function(previewContentsElt) { + var tocExp = new RegExp("^\\s*" + toc.config.marker + "\\s*$"); + var tocEltList = document.querySelectorAll('.table-of-contents, .toc'); + var htmlToc = buildToc(previewContentsElt); + // Replace toc paragraphs + _each(previewContentsElt.getElementsByTagName('p'), function(elt) { + if(tocExp.test(elt.innerHTML)) { + elt.innerHTML = htmlToc; + } + }); + // Add toc in the TOC button + _each(tocEltList, function(elt) { + elt.innerHTML = htmlToc; + }); + } + + return toc; + } + + //=========== + // mathjax + function initMathJax() { + // 配置 + MathJax.Hub.Config({ + skipStartupTypeset: true, + "HTML-CSS": { + preferredFont: "TeX", + availableFonts: [ + "STIX", + "TeX" + ], + linebreaks: { + automatic: true + }, + EqnChunk: 10, + imageFont: null + }, + tex2jax: { inlineMath: [["$","$"],["\\\\(","\\\\)"]], displayMath: [["$$","$$"],["\\[","\\]"]], processEscapes: true }, + TeX: { + noUndefined: { + attributes: { + mathcolor: "red", + mathbackground: "#FFEEEE", + mathsize: "90%" + } + }, + Safe: { + allow: { + URLs: "safe", + classes: "safe", + cssIDs: "safe", + styles: "safe", + fontsize: "all" + } + } + }, + messageStyle: "none" + }); + + var mathJax = {}; + mathJax.config = { + tex : "{}", + tex2jax: '{ inlineMath: [["$","$"],["\\\\\\\\(","\\\\\\\\)"]], displayMath: [["$$","$$"],["\\\\[","\\\\]"]], processEscapes: true }' + }; + + mathJax.init = function(p) { + converter.hooks.chain("preConversion", removeMath); + converter.hooks.chain("postConversion", replaceMath); + }; + + // From math.stackexchange.com... + + // + // The math is in blocks i through j, so + // collect it into one block and clear the others. + // Replace &, <, and > by named entities. + // For IE, put
    at the ends of comments since IE removes \n. + // Clear the current math positions and store the index of the + // math, then push the math string onto the storage array. + // + function processMath(i, j, unescape) { + var block = blocks.slice(i, j + 1).join("") + .replace(/&/g, "&") + .replace(//g, ">"); + for(HUB.Browser.isMSIE && (block = block.replace(/(%[^\n]*)\n/g, "$1
    \n")); j > i;) + blocks[j] = "", j--; + blocks[i] = "@@" + math.length + "@@"; + unescape && (block = unescape(block)); + math.push(block); + start = end = last = null; + } + + function removeMath(text) { + if(!text) { + return; + } + start = end = last = null; + math = []; + var unescape; + if(/`/.test(text)) { + text = text.replace(/~/g, "~T").replace(/(^|[^\\])(`+)([^\n]*?[^`\n])\2(?!`)/gm, function(text) { + return text.replace(/\$/g, "~D") + }); + unescape = function(text) { + return text.replace(/~([TD])/g, + function(match, n) { + return {T: "~", D: "$"}[n] + }) + }; + } else { + unescape = function(text) { + return text + }; + } + + // + // The pattern for math delimiters and special symbols + // needed for searching for math in the page. + // + var splitDelimiter = /(\$\$?|\\(?:begin|end)\{[a-z]*\*?\}|\\[\\{}$]|[{}]|(?:\n\s*)+|@@\d+@@)/i; + var split; + + if(3 === "aba".split(/(b)/).length) { + split = function(text, delimiter) { + return text.split(delimiter) + }; + } else { + split = function(text, delimiter) { + var b = [], c; + if(!delimiter.global) { + c = delimiter.toString(); + var d = ""; + c = c.replace(/^\/(.*)\/([im]*)$/, function(a, c, b) { + d = b; + return c + }); + delimiter = RegExp(c, d + "g") + } + for(var e = delimiter.lastIndex = 0; c = delimiter.exec(text);) { + b.push(text.substring(e, c.index)); + b.push.apply(b, c.slice(1)); + e = c.index + c[0].length; + } + b.push(text.substring(e)); + return b + }; + } + + blocks = split(text.replace(/\r\n?/g, "\n"), splitDelimiter); + for(var i = 1, m = blocks.length; i < m; i += 2) { + var block = blocks[i]; + if("@" === block.charAt(0)) { + // + // Things that look like our math markers will get + // stored and then retrieved along with the math. + // + blocks[i] = "@@" + math.length + "@@"; + math.push(block) + } else if(start) { + // Ignore inline maths that are actually multiline (fixes #136) + if(end == inline && block.charAt(0) == '\n') { + if(last) { + i = last; + processMath(start, i, unescape); + } + start = end = last = null; + braces = 0; + } + // + // If we are in math, look for the end delimiter, + // but don't go past double line breaks, and + // and balance braces within the math. + // + else if(block === end) { + if(braces) { + last = i + } else { + processMath(start, i, unescape) + } + } else { + if(block.match(/\n.*\n/)) { + if(last) { + i = last; + processMath(start, i, unescape); + } + start = end = last = null; + braces = 0; + } else { + if("{" === block) { + braces++ + } else { + "}" === block && braces && braces-- + } + } + } + } else { + if(block === inline || "$$" === block) { + start = i; + end = block; + braces = 0; + } else { + if("begin" === block.substr(1, 5)) { + start = i; + end = "\\end" + block.substr(6); + braces = 0; + } + } + } + + } + last && processMath(start, last, unescape); + return unescape(blocks.join("")); + } + + // + // Put back the math strings that were saved, + // and clear the math array (no need to keep it around). + // + function replaceMath(text) { + text = text.replace(/@@(\d+)@@/g, function(match, n) { + return math[n] + }); + math = null; + return text + } + + // + // This is run to restart MathJax after it has finished + // the previous run (that may have been canceled) + // + function startMJ(toElem, callback) { + var preview = toElem; + pending = false; + HUB.cancelTypeset = false; + HUB.Queue([ + "Typeset", + HUB, + preview + ]); + // 执行完后, 再执行 + HUB.Queue(function() { + callback && callback(); + }); + } + + var ready = false, pending = false, preview = null, inline = "$", blocks, start, end, last, braces, math, HUB = MathJax.Hub; + + // + // Runs after initial typeset + // + HUB.Queue(function() { + ready = true; + HUB.processUpdateTime = 50; + HUB.Config({"HTML-CSS": {EqnChunk: 10, EqnChunkFactor: 1}, SVG: {EqnChunk: 10, EqnChunkFactor: 1}}) + }); + + mathJax.init(); + return { + convert: startMJ + } + } + + function initUml() { + //=========== + // uml + var umlDiagrams = {}; + umlDiagrams.config = { + flowchartOptions: [ + '{', + ' "line-width": 2,', + ' "font-family": "sans-serif",', + ' "font-weight": "normal"', + '}' + ].join('\n') + }; + + var _loadUmlJs = false; + + // callback 执行完后执行 + umlDiagrams.convert = function(target, callback) { + var previewContentsElt = target; + + var sequenceElems = previewContentsElt.querySelectorAll('.prettyprint > .language-sequence'); + var flowElems = previewContentsElt.querySelectorAll('.prettyprint > .language-flow'); + + function convert() { + _each(sequenceElems, function(elt) { + try { + var diagram = Diagram.parse(elt.textContent); + var preElt = elt.parentNode; + var containerElt = crel('div', { + class: 'sequence-diagram' + }); + preElt.parentNode.replaceChild(containerElt, preElt); + diagram.drawSVG(containerElt, { + theme: 'simple' + }); + } + catch(e) { + console.trace(e); + } + }); + _each(flowElems, function(elt) { + try { + + var chart = flowchart.parse(elt.textContent); + var preElt = elt.parentNode; + var containerElt = crel('div', { + class: 'flow-chart' + }); + preElt.parentNode.replaceChild(containerElt, preElt); + chart.drawSVG(containerElt, JSON.parse(umlDiagrams.config.flowchartOptions)); + } + catch(e) { + console.error(e); + } + }); + + callback && callback(); + } + + if(sequenceElems.length > 0 || flowElems.length > 0) { + if(!_loadUmlJs) { + loadJs('/public/libs/md2html/uml.js', function() { + _loadUmlJs = true; + convert(); + }); + } else { + convert(); + } + } else { + callback && callback(); + } + }; + + return umlDiagrams; + } + + // extra是实时的, 同步进行 + initMarkdownExtra(); + + var m; + window.md2Html = function(mdText, toElem, callback) { + var _umlEnd = false; + var _mathJaxEnd = false; + + // 如果是jQuery对象 + if(!toElem['querySelectorAll'] && toElem['get']) { + toElem = toElem.get(0); + } + function _go(mdText, toElem) { + var htmlParsed = converter.makeHtml(mdText); + toElem.innerHTML = htmlParsed; + + // 同步执行 + var toc = initToc(); + toc.convert(toElem); + + // 异步执行 + var umlDiagrams = initUml(); + umlDiagrams.convert(toElem, function() { + _umlEnd = true; + if(_mathJaxEnd) { + callback && callback(toElem.innerHTML); + } + }); + } + + // 表示有mathjax? + // 加载mathJax + if(mdText.indexOf('$') !== -1) { + loadJs("/public/libs/MathJax/MathJax.js?config=TeX-AMS_HTML", function() { + // loadJs("http://cdn.bootcss.com/mathjax/2.5.3/MathJax.js?config=TeX-AMS_HTML", function() { + if(!m) { + var m = initMathJax(); + } + // 放到后面, 不然removeMathJax()不运行, bug + _go(mdText, toElem); + m.convert(toElem, function() { + _mathJaxEnd = true; + if(_umlEnd) { + callback && callback(toElem.innerHTML); + } + }); + }); + } else { + _mathJaxEnd = true; + _go(mdText, toElem); + } + } + +})();