Java集成editor.md开发markdonw程序

前言

本篇主要是使用Java语言结合开源editor.md进行开发markdown论坛功能,具体请看下图,如果不是你想要的,那可以看我另外一篇使用commonmark-java解析成html文档的文章

详细功能:
markdown界面很简洁,简书也就是这样,下图是你想要的吗?

详细功能图

原材料准备

editor.md

是国人开发的开源在线Markdown编辑器,单纯基于前端JavaScript,无需后台代码加持,适用于任何语言,我这里使用examples示例来进行改造,很久没更新了.....
首页地址:http://pandao.github.io/editor.md/

目录结构

项目搭建,资源准备

注意:这个步骤过程有点繁琐,尤其是一些资源文件的访问,所以可以根据自己的情况进行调整,但一定要看到第10步对应的界面,资源准备才算完成

  • 名词:下述所有的“外层”表示源码包的第一层目录
  1. 新建SpringBoot项目,这里不做赘述,不会的朋友请看超简单SpringBoot搭建
  2. 然后将外层examples文件夹中的css、js资源,拷贝到resources下的static中
  3. 将外层examples文件夹中的simple.html示例文件拷贝到工程的templates下面,如下图
资源准备
  1. 编辑simple.html文件,将如下图的资源文件路径根据你的项目进行调整
css资源
js资源
  1. 上图中的editormd.css和editormd.min.js需要从外层文件中拷贝进来,拷贝进来后如下图


    准备editormd.css和editormd.min.js
  2. 拷贝外层的lib目录


    lib目录加载
  3. 拷贝外层的fonts目录,并且将外层文件夹images中的loading.gif拷贝到我们项目的images中


    fonts目录,loading.gif文件
  4. 最终 调整后的simple.html文件内容为


    最终路径与内容对比
  5. 加入Controller类,以便于做后续操作


    image.png
  6. 启动服务器,要求能够访问simple.html,并且样式能够正常加载没有错误,如下图


    看到该图后才可进行下一步

开始开发,前方高能

开发主要分为3个层面

  1. 简单的后台提交内容示例。
  2. 前台接收markdown内容页面显示时进行转换加载。
  3. 复杂的服务器端开发(带文件上传)

1、后台内容提交页面

该页面即simple.html页面,核心的加载的JS在代码在页面底部,如下:

 var testEditor;

    $(function() {
        testEditor = editormd("test-editormd", {
            width   : "90%",
            height  : 640,
            syncScrolling : "single",
            path    : "lib/"
        });

    });
开发提交功能

开始进入简单功能的开发,其JS的写法,也可以看examples中其他页面,对应不同的功能,有不同的JS,或者直接跟我进入开发,保证不带沟里

  • simple界面中,加入form表单,修改JS,简单的提交客户端完成,内容如下:
simple界面中的form

    <!--在外层定义一个form,用来取值和提交表单-->
    <form name="mdEditorForm">


    <div id="test-editormd">
                <!--该区域内容为文字内容,非html内容-->
                <textarea name="content" id="content" style="display:none;">这是我要提交的内容啊

</textarea>
    </div>

    </form>

testEditor中JS的变化
            /**下述为新增,上面一行记得加逗号结束*/
            /*指定需要显示的功能按钮*/
            toolbarIcons : function() {
                return ["undo", "redo", "|","bold", "italic","ucwords","uppercase","lowercase","|","h1","h2","h3","h4","h5","h6","|","list-ul","list-ol","table","datetime","hr",  "||",  "watch", "fullscreen", "preview", "releaseIcon", "index"]
            },

            /*自定义功能按钮,下面我自定义了2个,一个是发布,一个是返回首页*/
            toolbarIconTexts : {
                releaseIcon : "<span bgcolor=\"gray\">发布</span>",
                index : "<span bgcolor=\"red\">返回首页</span>",
            },

            /*给自定义按钮指定回调函数*/
            toolbarHandlers:{
                releaseIcon : function(cm, icon, cursor, selection) {
                        contentCommit();//提交表单代码在下面
                        console.log("日志输出 testIcon =>", this, cm, icon, cursor, selection);
                },
                index : function(){
                    window.location.href = '返回首页的路径.html';
                },
            }

另外需要提供提交JS的代码

    /*提交表单的js*/
    function contentCommit(){
        mdEditorForm.method = "post";
        mdEditorForm.action = "contentCommit";//提交至服务器的路径
        mdEditorForm.submit();
    }
  • 修改Controller,加入接收提交内容的方法,并且提供一个简单的view界面,用于预览,如下


    服务器端代码

上图代码如下:

@Controller
@RequestMapping
public class MarkdownController {

    @RequestMapping("simple")
    public ModelAndView simple(){
        //这里的文件名将直接定位到templates下面的文件
        ModelAndView modelAndView = new ModelAndView("simple");
        System.out.println("进入内容编写页面");
        return modelAndView;
    }

    @RequestMapping("contentCommit")
    public ModelAndView contentCommit(String content){
        System.out.println("提交的内容为:" + content);
        ModelAndView modelAndView = new ModelAndView("view");
        //将内容发送至前台预览
        modelAndView.addObject("viewContent" , content);
        System.out.println("跳转至内容显示页面");
        return modelAndView;
    }

}
修改后的markdown页面
  • 启动服务器,并且点击提交,你会发现内容正确到达客户端。

简单的提交完成,接下来,开发预览界面


2. 前台加载markdown语法,并且解析

  1. 拷贝examples中的html-preview-markdown-to-html.html页面,改名为view.html界面,将其中依赖的静态资源文件全部调整正确, 并且访问不报错误,所以你需要将不存在的文件有序的拷贝进来

    • 新依赖外层css中的editormd.preview.css文件
    • 新依赖外层的editormd.js,或直接将内容改为editormd.min.js,两者效果一样
    • 新依赖examples中的test.md文件,jquery.min.js所依赖,可以修改jquery文件,移除掉
  2. 更改完成后的内容页面如下,控制台不能够报错

预览界面的内容

预览界面重要代码如下

预览JS图
  1. 前台页面textarea id="append-test"中的内容全部清除,将上图JS中的test.md改为内容请求地址,后台提供一个rest内容获取接口
     /**
     * 读取所保存的markdown数据
     * @return
     */
    @RequestMapping("getContent")
    @ResponseBody
    public String getContent(){
        return "### 这是markdown格式的内容,暂时固定写死,应从数据库读取上个接口保存的内容";
    }
更改后的JS代码
点击发布后,出现的预览界面

到此为止,简单的markdonw集成进来了,其中的按钮以及js用法,还需要结合其他的页面进行进一步探讨,整体流程效果图如下:

整体流程效果图

3. 剪切板文件上传代码加入

  1. 很多时候我们上传文件不会手动去写markdown的文件语法,也不会去点击markdown提供的文件上传按钮,更多的是通过Ctrl+C、Ctrl+V来读取剪贴板实现上传,类似于简书、csdn等博客都有实现该功能。但在如下代码中,剪切板的读取存在浏览器兼容的问题,经测试,目前只能读取QQ等应用Ctrl+Alt+A截图的图片,无法读取在桌面右键->复制的图片,望各位悉知,效果图如下
aa.gif
  1. 编写JS代码


    JS剪切板读取
        //读取剪切板
        $("#test-editormd").on('paste', function (ev) {
            var topicCode = $("#topicCode").val();
            //详细可查看clipboardData属性的使用方式
            var data = (ev.clipboardData || ev.originalEvent.clipboardData);
            var items1 = data.items;
            console.log(items1);//输出 DataTransferItem对象
            var imageFile;
            for(var index in items1){
                var item = items1[index];
                //如果kind是file,可以用getAsFile()方法获取到文件
                if (item.kind === 'file') {
                    imageFile = item.getAsFile();
                    console.log('获取到剪贴板的文件' + item.kind);
                    break;
                }else if(item.kind === 'string'){
                    console.log('获取到剪贴板的字符串' + item.kind);
                }
            }

            //执行上传
            uploadTrigger(imageFile,topicCode);
        });

上图中uploadTrigger(imageFile,topicCode) 函数为文件上传的js代码,如下:

文件上传代码片段
    //执行上传
    function uploadTrigger(imageFile,topicCode){
        //topicCode为文章代码,需要在关联图片,从而实现预览时准确加载到图片
        var url = "uploadMdFile.json?topicCode="+topicCode;
        var formdata = new FormData();
        formdata.append("file", imageFile);

            $.ajax({
                url: url,
                type: "post",
                data: formdata,
                //关闭序列化
                processData: false,
                contentType: false,
                success: function (data) {
                    //data为后台返回的retMap数据
                    if(data.retCode == "success"){
                        //向markdown区域插入该格式的值,从而实现图片在右侧显示
                        testEditor.insertValue("\n![" + data.file + "](" + data.rootPath + ")");
                    } else {
                        console.log(data.msg);
                    }
                },
                error : function(data){
                }
            });
    }
  1. 页面JS完成后,需要实现后台服务端接收并存储图片,代码片段如下,图片我这里没有存储在数据库中,而是直接写在了本地服务器能够访问的地方,可根据实际情况进行改变目录或者上传至OSS存储服务器
image.png
    /**
     * 上传文件
     * @return
     */
    @RequestMapping("uploadMdFile.json")
    @ResponseBody
    public Map<String,Object> getContent(HttpServletRequest request,@RequestParam(value = "file",required=false) MultipartFile file,String topicCode){
        Map<String,Object> resultMap = new HashMap<>();
        if(topicCode != null && !"".equals(topicCode)){
            //该CODE用于对应图片存储,实际项目中需要存储该文章与图片的关系,我这不做处理
            System.out.println("主题CODE->" + topicCode);
        }
        try {
            // 检测是不是存在上传文件
            boolean isMultipart = ServletFileUpload.isMultipartContent(request);
            if(isMultipart){
                MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
                Map<String, MultipartFile> multipartFileMap = multipartRequest.getFileMap();
                for (Map.Entry<String, MultipartFile> entryFile : multipartFileMap.entrySet()) {
                    MultipartFile value = entryFile.getValue();
                    //读取输入流
                    InputStream is = value.getInputStream();
                    //获取文件名
                    String fileName = value.getOriginalFilename();
                    //声明byte缓冲数组
                    byte[] b = new byte[(int) value.getSize()];
                    is.read(b);
                    //将文件名上传的name作为返回的key,默认为file
                    resultMap.put(entryFile.getKey() , fileName);
                    //返回接口调用状态码
                    resultMap.put("retCode" , "success");
                    //返回图片访问路径,此处可以改为OSS分布式存储,根据项目具体情况调整
                    resultMap.put("rootPath" , "http://localhost:8080/"+fileName);
                    //上传到文件服务器路径,此处我直接上传到项目部署编译路径,需要调整
                    OutputStream os = new FileOutputStream(new File("F:\\work\\testjava\\md\\target\\classes\\static" , fileName));
                    os.write(b);
                    os.flush();
                }
            }
        }catch (Exception e){
        }
        return resultMap;
    }

上述代码中 F:\\work\\testjava\\md\\target\\classes\\static 指的是我的代码编译目录,该目录下的文件可以通过:http://localhost:8080/xxx 进行访问,请根据自身情况进行调整

先暂时更新到这里,具体的功能基本已经实现,若需要读取桌面文件,则可能需要实现文本框也支持图片流,可以采用H5的FileReader实现,后续再更....

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 27,102评论 1 45
  • 觉得自己做的到和不做的到,其实只在一念之间
    明珠Poppy阅读 164评论 0 0
  • CentOS 7 中 Docker 的安装 Docker 软件包已经包括在默认的 CentOS-Extras 软件...
    firehole阅读 599评论 0 0
  • “艾玛,我忘记还信用卡了,征信要有污点了,怎么办?” 这个问题,是我亲自经历过。有一个月,记忆短路到了信用卡还款日...
    小西读财阅读 296评论 0 0