AdminEAP框架—web集成Markdown组件

来源:互联网 发布:win764位系统优化 编辑:程序博客网 时间:2024/04/28 15:52

AdminEAP

                         AdminEAP框架

1、Markdown介绍

Markdown 是一种轻量级的「标记语言」,它的优点很多,目前也被越来越多的写作爱好者,撰稿者广泛使用。看到这里请不要被「标记」、「语言」所迷惑,Markdown 的语法十分简单。常用的标记符号也不超过十个,这种相对于更为复杂的HTML 标记语言来说,Markdown 可谓是十分轻量的,学习成本也不需要太多,且一旦熟悉这种语法规则,会有一劳永逸的效果。

尤其作为程序员而言,需要经常编写技术文档,使用Markdown编辑器效率很高,你们现在看到的这篇博客就是CSDN的Markdown编辑器编写的。有关Markdown的介绍可以查阅Marddown的相关资料。

一直想找一款可以免费使用的markdown编辑器,网上也有很多推荐,目前比较流行的是StackEdit(CSDN的markdown插件是基于StackEdit修改的)和cmd Markdown,有些插件需要注册和收费。同时,需要经常写个人博客和技术文档,决定在开源项目AdminEAP中集成Markdown。

本文讲述的是在AdminEAP框架(一个在AdminLTE上实现的系统管理框架,已在Github上开源)下基于Editor.md实现的Markdown在线编辑组件。

以下是实现效果

AdminEAP Markdown

2、Markdown组件比较

经过一番调研,目前在Github上人气比较旺的Markdown组件相关项目有stackedit,markdown-js,markdown-it,strapdown,editor.md,以下做个简单的比较。

  • stackedit: 功能比较齐全,包含了服务端,而我只想要前端的功能,所以舍弃了
  • markdown-js:不支持正常的代码解析,弃用
  • markdown-it:nodejs编写,有详细的技术文档,但缺少demo,弃用
  • strapdown: 解析功能不够强大,不支持时序图、流程图、数学公式等比较复杂的解析,但使用比较简单,结果样式比较多,比较美观,可以考虑用于比较简单markdown文件的解析显示
  • editor.md 解析功能强大,demo比较齐全,对于web开发者来说易于理解,所以选择editor.md作为AdminEAP项目的markdown组件开发基础。

3、实现

AdminEAP中markdown组件使用浏览器缓存离线存储,只要不清除缓存,文件不会丢失。
关键字使用bootstrap-tagsinput组件

Markdown编辑

关键代码 markdown_add.html

<link rel="stylesheet" href="${basePath}/resources/common/libs/markdown/editor.md/css/editormd.min.css"><link rel="stylesheet" href="${basePath}/resources/common/libs/tagsinput/bootstrap-tagsinput.css"><link rel="stylesheet" href="${basePath}/resources/common/libs/tagsinput/app.css"><section class="content-header">    <h1>        <span>MarkDown编辑</span>        <small>新增</small>    </h1>    <ol class="breadcrumb">        <li><a href="#"><i class="fa fa-dashboard"></i> 首页</a></li>        <li><a href="#">系统管理</a></li>        <li class="active">Markdown编辑器</li>    </ol></section><section class="content">    <div class="row form-horizontal">        <div class="col-sm-12">            <div class="box box-primary">                <div class="box-header bg-info" style="padding-bottom: 0">                    <div class="row" style="padding-bottom:5px;">                        <div class="col-sm-7">                            <div clas="form-group">                              <input class="form-control" type="text" placeholder="请输入文章标题……">                            </div>                        </div>                        <div class="col-sm-5">                            <div clas="form-group">                                <input  type="text" id="keywords" data-role="tagsinput" placeholder="关键字" >                            </div>                        </div>                    </div>                    <div class="row">                       <div class="form-group col-sm-7">                        <div class="col-sm-4">                            <select id="editormd-theme-select" class="form-control">                                <option selected="selected" value="">工具栏样式</option>                            </select>                        </div>                        <div class="col-sm-4">                            <select id="editor-area-theme-select" class="form-control">                                <option selected="selected" value="">编辑区样式</option>                            </select>                        </div>                        <div class="col-sm-4">                            <select id="preview-area-theme-select" class="form-control">                                <option selected="selected" value="">预览区样式</option>                            </select>                        </div>                    </div>                       <div class="form-group col-sm-5 pull-right">                        <div class="col-sm-6">                            <label class="control-label">                                <input type="checkbox" name="autoHeight" data-flag="icheck" class="flat-red"                                       id="autoHeight"> 自适应高度                            </label>                        </div>                        <div class="col-sm-6 text-right">                            <div class="btn-group ">                                <button class="btn btn-default"><i class="fa fa-file-pdf-o">&nbsp;&nbsp;导出PDF</i>                                </button>                                <button class="btn btn-primary"><i class="fa fa-save">&nbsp;&nbsp;提交</i></button>                            </div>                        </div>                    </div>                    </div>                </div>                <div clas="box-body">                    <div id="editormd">                        <textarea style="display:none;"></textarea>                    </div>                </div>            </div>        </div>    </div>    </div></section><script type="text/javascript" src="${basePath}/resources/common/libs/markdown/editor.md/js/editormd.js"></script><script type="text/javascript" src="${basePath}/resources/common/libs/tagsinput/bootstrap-tagsinput.min.js"></script><script>    var editor;    function themeSelect(id, themes, lsKey, callback) {        var select = $("#" + id);        for (var i = 0, len = themes.length; i < len; i++) {            var theme = themes[i];            var selected = (localStorage[lsKey] == theme) ? " selected=\"selected\"" : "";            select.append("<option value=\"" + theme + "\"" + selected + ">" + theme + "</option>");        }        select.bind("change", function () {            var theme = $(this).val();            if (theme === "") {                return false;            }            localStorage[lsKey] = theme;            callback(select, theme);        });        return select;    }    $(function () {        //高度自定义框        $("#autoHeight").iCheck({            checkboxClass: 'icheckbox_flat-green',            radioClass: 'iradio_flat-green'        });        var isAutoHeight = localStorage.isAutoHeight ? localStorage.isAutoHeight : false;        if (isAutoHeight == "false" || !isAutoHeight) {            $("#autoHeight").iCheck("uncheck");        } else {            $("#autoHeight").iCheck("check");        }        $("#autoHeight").on("ifChanged", function (event) {            isAutoHeight = $("#autoHeight").prop("checked");            localStorage.isAutoHeight = isAutoHeight;        });        //markdown 默认内容        var markdownContent = null;        $.ajax({            type: "get",            url: basePath + "/resources/common/libs/markdown/readme.md",            async: false,            success: function (md) {                markdownContent = localStorage.markdownContent ? localStorage.markdownContent : md;            }        });        //keywords 关键字        $("#keywords").on("change",function(event){             var $element=$(event.target);             var val = $element.val();             //修复placeholder问题             if(val){                 $element.prev(".bootstrap-tagsinput").find("input:eq(0)").attr("placeholder",null);             }else{                 $element.prev(".bootstrap-tagsinput").find("input:eq(0)").attr("placeholder","关键字");             }        });        //高度不自定义时高度        var clientHeight = (document.body.clientHeight < document.documentElement.clientHeight) ? document.body.clientHeight : document.documentElement.clientHeight;        editor = editormd("editormd", {            width: "100%",            height: clientHeight,            theme: (localStorage.theme) ? localStorage.theme : "default",            previewTheme: (localStorage.previewTheme) ? localStorage.previewTheme : "default",            editorTheme: (localStorage.editorTheme) ? localStorage.editorTheme : "default",            path: basePath + '/resources/common/libs/markdown/editor.md/lib/',            autoHeight: isAutoHeight == "true" ? true : false,            htmlDecode: "style,script,iframe",            tex: true,            emoji: true,            taskList: true,            flowChart: true,            sequenceDiagram: true,            markdown: markdownContent,            //图片上传            imageUpload: true,            imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp"],            imageUploadURL: "${basePath}/file/markdownUpload",            onchange: function () {                localStorage.markdownContent = editor.getMarkdown();            }            /*             上传的后台只需要返回一个 JSON 数据,结构如下:             {             success : 0 | 1,           // 0 表示上传失败,1 表示上传成功             message : "提示的信息,上传成功或上传失败及错误信息等。",             url     : "图片地址"        // 上传成功时才返回             }             */        });        //editor.clear();        /* $.get(basePath + "/resources/common/libs/markdown/readme.md", function (md) {         setTimeout(function () {         editor.appendMarkdown(md)         }, 500);         });*/        themeSelect("editormd-theme-select", editormd.themes, "theme", function ($this, theme) {            editor.setTheme(theme);        });        themeSelect("editor-area-theme-select", editormd.editorThemes, "editorTheme", function ($this, theme) {            editor.setCodeMirrorTheme(theme);        });        themeSelect("preview-area-theme-select", editormd.previewThemes, "previewTheme", function ($this, theme) {            editor.setPreviewTheme(theme);        });    });</script>

4、图片上传

 imageUpload: true, imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp"], imageUploadURL: "${basePath}/file/markdownUpload",

后台实现代码

 /**     * markdown组件上传图片     */    @RequestMapping(value = "/markdownUpload", method = RequestMethod.POST)    @ResponseBody    public MarkDownResult markdownUpload(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "editormd-image-file", required = false) MultipartFile attach) {        try {            String relPath = PropertiesUtil.getValue("markdownPath");            String dirPath = request.getRealPath("/");            //不存在目录 则创建            File filePath = new File(dirPath + relPath);            if (!filePath.exists()) {                filePath.mkdirs();            }            File realFile = new File(dirPath + relPath + File.separator + attach.getOriginalFilename());            //上传的文件已存在 则对新上传的文件重命名            if (realFile.exists()) {                String fileName = DateUtil.format(new Date(), "yyyyMMddHHmmss") + "_" + attach.getOriginalFilename();                realFile = new File(dirPath+relPath+File.separator+fileName);            }            boolean iscreate = FileUtil.copyInputStreamToFile(attach.getInputStream(), realFile);            if (iscreate) {                String url=relPath + File.separator + realFile.getName();                url=request.getAttribute("basePath").toString()+url.replaceAll("\\\\","/");                return new MarkDownResult(1, "上传成功", url);            }            else {                return new MarkDownResult(0, "上传失败", null);            }        } catch (IOException ex) {            return new MarkDownResult(0, "上传失败:原因" + ex.getMessage().toString(), null);        }    }

5、文章预览

实现效果
文章预览
关键代码

<link rel="stylesheet" href="${basePath}/resources/common/libs/markdown/editor.md/css/editormd.min.css"><section class="content-header">    <h1>        <span>我的文章</span>        <small id="title_sm">预览</small>    </h1>    <ol class="breadcrumb">        <li><a href="#"><i class="fa fa-dashboard"></i> 首页</a></li>        <li><a href="#">系统管理</a></li>        <li class="active">我的文章</li>    </ol></section><section class="content">    <div class="row form-horizontal">        <div class="col-md-12">            <div class="box box-primary">                <div class="box-header text-center">                        <div clas="form-group">                            <label id="title" style="font-size:21px;font-weight:normal"></label>                        </div>                        <div clas="form-group">                            <label style="font-weight: normal">关键字:</label>                            <label id="keywords"></label>                        </div>                </div>                <div clas="box-body">                    <div id="editormd">                        <textarea id="append-test" style="display:none;"></textarea>                    </div>                </div>            </div>        </div>    </div>    <div class="navbar-fixed-middle">        <div class="box box-primary box-solid">            <div class="box-header with-border">                <h4 class="box-title" style="padding-right:20px;"><i class="fa fa-hand-o-left">&nbsp;文章目录</i></h4>                <div class="box-tools pull-right">                    <button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i>                    </button>                </div>                <!-- /.box-tools -->            </div>            <!-- /.box-header -->            <div class="box-body" style="padding-left:0">                <div  id="custom-toc-container"></div>            </div>            <!-- /.box-body -->        </div>    </div>    </div>    </div></section><script type="text/javascript" src="${basePath}/resources/common/libs/markdown/editor.md/lib/marked.min.js"></script><script type="text/javascript" src="${basePath}/resources/common/libs/markdown/editor.md/lib/prettify.min.js"></script><script type="text/javascript" src="${basePath}/resources/common/libs/markdown/editor.md/lib/raphael.min.js"></script><script type="text/javascript" src="${basePath}/resources/common/libs/markdown/editor.md/lib/underscore.min.js"></script><script type="text/javascript" src="${basePath}/resources/common/libs/markdown/editor.md/lib/sequence-diagram.min.js"></script><script type="text/javascript" src="${basePath}/resources/common/libs/markdown/editor.md/lib/flowchart.min.js"></script><script type="text/javascript" src="${basePath}/resources/common/libs/markdown/editor.md/lib/jquery.flowchart.min.js"></script><script type="text/javascript" src="${basePath}/resources/common/libs/markdown/editor.md/js/editormd.min.js"></script><script>    var editor;   /* var height=$(document).height();    $(".navbar-fixed-middle").css({"top":height/2+"px"});*/    $(function () {        var mid = "${id?default('')}";        ajaxPost(basePath + "/markdown/get/" + mid, null, function (result) {            $("#title").text(result.title);            $("#keywords").text(result.keywords);            editor = editormd.markdownToHTML("editormd", {                markdown: result.content,                htmlDecode: "style,script,iframe",  // you can filter tags decode                emoji: true,                tocm:true ,    // Using [TOCM]                tocContainer: "#custom-toc-container", // 自定义 ToC 容器层                tocDropdown:true,                taskList: true,                tex: true,  // 默认不解析                flowChart: true,  // 默认不解析                sequenceDiagram: true // 默认不解析            });        });    });    function returnToList() {        loadPage(basePath + "/markdown/list");    }</script>

关键样式

.navbar-fixed-middle {    position: fixed;    z-index: 1031;    bottom:30px;    right: 15px;    margin-bottom: 0;    border-width: 1px 0 0;}

6、附录

最终代码以Github上的代码为准。
AdminEAP代码:https://github.com/bill1012/AdminEAP

AdminEAP Markdown:http://www.admineap.com

欢迎大家留言至我的邮箱 admin@admineap.com 或 jrn1012@petrochina.com.cn

1 0
原创粉丝点击