使用editor.md踩坑血泪史总结

当我们的项目需要引入一个功能强大的markdown编辑器,我们兴许会考虑使用当今比较主流的markdown编辑器:editor.md,最重要的是它完全开源,免费。

这是它的官方地址:https://pandao.github.io/editor.md/

然而这个编辑器并不是拿来即用的,引入的时候可能会遇到一些棘手的问题和莫名其妙的错误,而且这些错误在网上百度却很难快速找到行之有效的解决方案。

写在前面

首先要注意的是,editor.md是依赖于 jQuery 的,所以必须先引入 jQuery 文件(或 jQuery对象),再引入editor.md的 js文件

解决editor.md图片上传问题

首先我们来看一下后端接口是怎么写的,后端使用的是java的springboot框架,写了一个文件上传的接口,它负责接收一个参数名为editormd-image-file的文件,返回一个json字符串,这个json字符串也要满足editor.md所要求的:上传成功返回文件url路径和success状态码1,上传失败则返回success状态码0

/**
	 * 该方法值负责Editor.md的图片上传,因为Editor.md要求返回的json数据是固定格式 同时限制了上传文件的类型只可以是图片
	 * 并且为了访问起来方便,直接返回文件的绝对URL,后端把字符串拼接好,前端不用拼接字符串了
	 * 
	 * @param file    editormd-image-file 这是Editor.md上传文件固定的表单参数
	 * @param request 需要request对象用以动态生成web uri
	 * @return 返回的是文件的绝对URL,可直接定位到图片资源
	 */
	@LogRecord("Editor.md图片上传")
	@ApiOperation("Editor.md图片上传")
	@RequestMapping("/common/editor/md/upload/file")
	public JSONObject uploadFileForEditor(@RequestParam(value = "editormd-image-file") MultipartFile file,
			HttpServletRequest request) {
		// response.setHeader("X-Frame-Options", "SAMEORIGIN");// 解决IFrame拒绝的问题
		JSONObject res = new JSONObject();
		if (file.isEmpty()) {
			res.put("success", 0);// success=0表示上传失败
			res.put("message", TourConstant.MESSAGE_FILE_CAN_NOT_BE_NULL);
			return res;
		}
		String fileName = file.getOriginalFilename(); // 文件名
		String suffixName = fileName.substring(fileName.lastIndexOf(".")); // 后缀名
		if (!fileSuffixs.contains(suffixName)) {
			res.put("success", 0);// success=0表示上传失败
			res.put("message", TourConstant.MESSAGE_FILE_SUFFIX_NOT_ALLOW);
			return res;
		}
		fileName = UUID.randomUUID().toString().replace("-", "").concat(suffixName);// 新文件名
		String filePath = TourConstant.ATTR_UPLOAD_LOCATION; // 上传后的路径
		String dateStr = dateFormat.format(new Date());
		filePath = filePath.concat(dateStr).concat("\\");
		File dest = new File(filePath + fileName);
		if (!dest.getParentFile().exists()) {
			dest.getParentFile().mkdirs();
		}
		try {
			file.transferTo(dest.getAbsoluteFile());
			String scheme = request.getScheme();
			String serverName = request.getServerName();
			String contextPath = request.getContextPath();
			int serverPort = request.getServerPort();
			String prefix = scheme + "://" + serverName + contextPath + ":" + serverPort
					+ TourConstant.ATTR_UPLOAD_FILE_URI_PREFIX;
			res.put("url", prefix + dateStr + "/" + fileName);
			res.put("success", 1);
			res.put("message", "upload success!");
			return res;
		} catch (IOException e) {
			e.printStackTrace();
			res.put("success", 0);// success=0表示上传失败
			res.put("message", "upload failed!");
			return res;
		}
	}

而我们下载好的editor.md,它的目录结构大致如下(下载解压后名字为editor.md-master,我把它重命名为了editormd):
在这里插入图片描述

我们只需要找到plugins目录下的image-dialog

在这里插入图片描述

将其中的image-dialog.js替换为如下代码:

/*!
 * Image (upload) dialog plugin for Editor.md
 *
 * @file        image-dialog.js
 * @author      pandao
 * @version     1.3.4
 * @updateTime  2015-06-09
 * {@link       https://github.com/pandao/editor.md}
 * @license     MIT
 */

(function() {

    var factory = function (exports) {

		var pluginName   = "image-dialog";

		exports.fn.imageDialog = function() {

            var _this       = this;
            var cm          = this.cm;
            var lang        = this.lang;
            var editor      = this.editor;
            var settings    = this.settings;
            var cursor      = cm.getCursor();
            var selection   = cm.getSelection();
            var imageLang   = lang.dialog.image;
            var classPrefix = this.classPrefix;
            var iframeName  = classPrefix + "image-iframe";
			var dialogName  = classPrefix + pluginName, dialog;

			cm.focus();

            var loading = function(show) {
                var _loading = dialog.find("." + classPrefix + "dialog-mask");
                _loading[(show) ? "show" : "hide"]();
            };

            if (editor.find("." + dialogName).length < 1)
            {
                var guid   = (new Date).getTime();
                var action = settings.imageUploadURL + (settings.imageUploadURL.indexOf("?") >= 0 ? "&" : "?") + "guid=" + guid;

                if (settings.crossDomainUpload)
                {
                    action += "&callback=" + settings.uploadCallbackURL + "&dialog_id=editormd-image-dialog-" + guid;
                }

              /*  var dialogContent = ( (settings.imageUpload) ? "<form action=\"" + action +"\" target=\"" + iframeName + "\" method=\"post\" enctype=\"multipart/form-data\" class=\"" + classPrefix + "form\">" : "<div class=\"" + classPrefix + "form\">" ) +
                                        ( (settings.imageUpload) ? "<iframe name=\"" + iframeName + "\" id=\"" + iframeName + "\" guid=\"" + guid + "\"></iframe>" : "" ) +
                                        "<label>" + imageLang.url + "</label>" +
                                        "<input type=\"text\" data-url />" + (function(){
                                            return (settings.imageUpload) ? "<div class=\"" + classPrefix + "file-input\">" +
                                                                                "<input type=\"file\" name=\"" + classPrefix + "image-file\" accept=\"image/!*\" />" +
                                                                                "<input type=\"submit\" value=\"" + imageLang.uploadButton + "\" />" +
                                                                            "</div>" : "";
                                        })() +
                                        "<br/>" +
                                        "<label>" + imageLang.alt + "</label>" +
                                        "<input type=\"text\" value=\"" + selection + "\" data-alt />" +
                                        "<br/>" +
                                        "<label>" + imageLang.link + "</label>" +
                                        "<input type=\"text\" value=\"http://\" data-link />" +
                                        "<br/>" +
                                    ( (settings.imageUpload) ? "</form>" : "</div>");*/
                //这是我个人写法
                var dialogContent = ( (settings.imageUpload) ? "<form action=\"#\" target=\"" + iframeName + "\" method=\"post\" enctype=\"multipart/form-data\" class=\"" + classPrefix + "form\">" : "<div class=\"" + classPrefix + "form\">" ) +
                    ( (settings.imageUpload) ? "<iframe name=\"" + iframeName + "\" id=\"" + iframeName + "\" guid=\"" + guid + "\"></iframe>" : "" ) +
                    "<label>" + imageLang.url + "</label>" +
                    "<input type=\"text\" data-url />" + (function(){
                        return (settings.imageUpload) ? "<div class=\"" + classPrefix + "file-input\">" +
                            "<input type=\"file\" name=\"" + classPrefix + "image-file\" id=\"" + classPrefix + "image-file\" accept=\"image/!*\" />" +
                            "<input type=\"submit\" value=\"" + imageLang.uploadButton + "\" />" +
                            "</div>" : "";
                    })() +
                    "<br/>" +
                    "<label>" + imageLang.alt + "</label>" +
                    "<input type=\"text\" value=\"" + selection + "\" data-alt />" +
                    "<br/>" +
                    "<label>" + imageLang.link + "</label>" +
                    "<input type=\"text\" value=\"http://\" data-link />" +
                    "<br/>" +
                    ( (settings.imageUpload) ? "</form>" : "</div>");



				//这个是作者注释掉的,不知道干啥的 忽略即可
                //var imageFooterHTML = "<button class=\"" + classPrefix + "btn " + classPrefix + "image-manager-btn\" style=\"float:left;\">" + imageLang.managerButton + "</button>";

                dialog = this.createDialog({
                    title      : imageLang.title,
                    width      : (settings.imageUpload) ? 465 : 380,
                    height     : 254,
                    name       : dialogName,
                    content    : dialogContent,
                    mask       : settings.dialogShowMask,
                    drag       : settings.dialogDraggable,
                    lockScreen : settings.dialogLockScreen,
                    maskStyle  : {
                        opacity         : settings.dialogMaskOpacity,
                        backgroundColor : settings.dialogMaskBgColor
                    },
                    buttons : {
                        enter : [lang.buttons.enter, function() {
                            var url  = this.find("[data-url]").val();
                            var alt  = this.find("[data-alt]").val();
                            var link = this.find("[data-link]").val();

                            if (url === "")
                            {
                                alert(imageLang.imageURLEmpty);
                                return false;
                            }

							var altAttr = (alt !== "") ? " \"" + alt + "\"" : "";

                            if (link === "" || link === "http://")
                            {
                                cm.replaceSelection("![" + alt + "](" + url + altAttr + ")");
                            }
                            else
                            {
                                cm.replaceSelection("[![" + alt + "](" + url + altAttr + ")](" + link + altAttr + ")");
                            }

                            if (alt === "") {
                                cm.setCursor(cursor.line, cursor.ch + 2);
                            }

                            this.hide().lockScreen(false).hideMask();

                            //删除对话框
                            this.remove();

                            return false;
                        }],

                        cancel : [lang.buttons.cancel, function() {
                            this.hide().lockScreen(false).hideMask();

                            //删除对话框
                            this.remove();
                            
                            return false;
                        }]
                    }
                });

                dialog.attr("id", classPrefix + "image-dialog-" + guid);

				if (!settings.imageUpload) {
                    return ;
                }

				var fileInput  = dialog.find("[name=\"" + classPrefix + "image-file\"]");

				fileInput.bind("change", function() {
					var fileName  = fileInput.val();
					var isImage   = new RegExp("(\\.(" + settings.imageFormats.join("|") + "))$", "i"); // /(\.(webp|jpg|jpeg|gif|bmp|png))$/

					if (fileName === "")
					{
						alert(imageLang.uploadFileEmpty);

                        return false;
					}

                    if (!isImage.test(fileName))
					{
						alert(imageLang.formatNotAllowed + settings.imageFormats.join(", "));

                        return false;
					}

                    loading(true);

                    var submitHandler = function() {

                        var uploadIframe = document.getElementById(iframeName);

                        uploadIframe.onload = function() {

                            loading(false);

                            /*var body = (uploadIframe.contentWindow ? uploadIframe.contentWindow : uploadIframe.contentDocument).document.body;
                            var json = (body.innerText) ? body.innerText : ( (body.textContent) ? body.textContent : null);

                            json = (typeof JSON.parse !== "undefined") ? JSON.parse(json) : eval("(" + json + ")");

                            console.log(json);

                            if(!settings.crossDomainUpload)
                            {
                              if (json.success === 1)
                              {
                                  dialog.find("[data-url]").val(json.url);
                              }
                              else
                              {
                                  alert(json.message);
                              }
                            }

                            return false;*/

                                //这是我个人写法
                               var formData = new FormData();
                                formData.append("editormd-image-file",$("#editormd-image-file")[0].files[0]);
                                var action = settings.imageUploadURL + (settings.imageUploadURL.indexOf("?") >= 0 ? "&" : "?") + "guid=" + guid;
                                //console.log(formData);

                                $.ajax({
                                    type:"post",
                                    url:action,
                                    data:formData,
                                    dataType:"json",
                                    async:false,
                                    processData : false, // 使数据不做处理
                                    contentType : false, // 不要设置Content-Type请求头
                                    success:function(data){
                                        // 成功拿到结果放到这个函数 data就是拿到的结果
                                        if(data.success == 1){
                                            console.log(data.message);
                                            dialog.find("[data-url]").val(data.url);
                                        }else{
                                            alert(data.message);
                                        }
                                    },
                                    error: function (res) {
                                        alert(res.responseText);
                                    }
                                });

                                return false;
                        };


                    };

                    dialog.find("[type=\"submit\"]").bind("click", submitHandler).trigger("click");


				});
            }

			dialog = editor.find("." + dialogName);
			dialog.find("[type=\"text\"]").val("");
			dialog.find("[type=\"file\"]").val("");
			dialog.find("[data-link]").val("http://");

			this.dialogShowMask(dialog);
			this.dialogLockScreen();
			dialog.show();

		};

	};

	// CommonJS/Node.js
	if (typeof require === "function" && typeof exports === "object" && typeof module === "object")
    {
        module.exports = factory;
    }
	else if (typeof define === "function")  // AMD/CMD/Sea.js
    {
		if (define.amd) { // for Require.js

			define(["editormd"], function(editormd) {
                factory(editormd);
            });

		} else { // for Sea.js
			define(function(require) {
                var editormd = require("./../../editormd");
                factory(editormd);
            });
		}
	}
	else
	{
        factory(window.editormd);
	}

})();


在引入该编辑器的时候,代码如下:
前提:已经引入了editor的所需的js和css文件

html部分:

<div class="layui-input-block" id="test-editor">
    <textarea id="content" style="display:none;">...</textarea>
</div>

js部分:

   editor = editormd("test-editor", {
       width: 950,
       height: 430,
       path: "../../../editormd/lib/",
       saveHTMLToTextarea: true,
       searchReplace: true,
       emoji: true,
       imageUpload: true,
       imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp"],
       saveHTMLToTextarea: true, //注意:这个配置,方便post提交表单
       crossDomainUpload: true,
       imageUploadURL: layui.setter.http + "/common/editor/md/upload/file",
   });

至此我相信文件上传的问题就能解决了。

解决editormd.js:2261 Uncaught TypeError: Cannot read property 'getValue' of undefined

这样的问题在什么时候会发生呢??

错误再现:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="../editor.md-master/css/editormd.css">
</head>
<body>
    <div class="layui-input-block" id="test-editor">
        <textarea id="content" style="display:none;">...</textarea>
    </div>
</body>
<script src="../jquery/jquery-1.7.2.min.js"></script>
<script src="../editor.md-master/editormd.js"></script>
<script type="text/javascript">
    $(function(){
        let editor = editormd("test-editor", {
            width: 950,
            height: 430,
            path: "../../../editor.md-master/lib/",
            saveHTMLToTextarea: true,
        });
        console.log(editor.getMarkdown());
    });
</script>
</html>

当声明了好editor 对象后,紧接着立马就调用它的getMarkdown()方法,就会报以上错误:
在这里插入图片描述
我们调用getMarkdown()方法, 而报错是getValue()未定义,看起来就很离谱。那肯定是getMarkdown()里面调用了getValue()这个方法,那既然没有报getMarkdown()未定义,说明该方法存在,该方法是人家editor.md开发人员写的,里面用到了getValue()方法,理论上getValue()肯定是定义好了,那为什么显示getValue()未定义呢?

解决:不要一开始立马就调用getMarkdown()方法,当editor对象创建好以后,让程序睡上500ms,再调用,就没事了。

( js学的不好,程序睡上500ms,只能用setTimeout()凑合凑合了)

    $(function(){
        let editor = editormd("test-editor", {
            width: 950,
            height: 430,
            path: "../../../editor.md-master/lib/",
            saveHTMLToTextarea: true,
        });
        setTimeout(function(){
            console.log(editor.getMarkdown());
        },500);
    });

也是无语,对象中的函数居然是懒加载的,非得等上一会儿这个对象才能完全创建好。这种问题还是第一次见,真的不容易解决,百度不出解决方案,记录一下。

好了,关于使用editor.md所遇到的问题,就分享到这里。

  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
Editor.md 是一个基于 Markdown 编辑器的开源项目,可以方便地在 Web 应用中使用。而 Flask 是一个轻量级的 Python Web 框架,非常适合用于构建小型的 Web 应用程序。下面是 Editor.md 在 Flask 中的使用方法。 首先,我们需要使用以下命令安装 editor.md: ``` npm install editor.md ``` 安装完成后,我们可以在 Flask 项目中创建一个静态文件夹(例如 static),将编辑器的 JS 和 CSS 文件放入其中。 在 Flask 的路由文件中,我们可以添加以下代码来渲染编辑器页面: ```python from flask import Flask, render_template app = Flask(__name__) @app.route('/editor') def editor(): return render_template('editor.html') if __name__ == '__main__': app.run() ``` 在 templates 文件夹中创建一个名为 editor.html 的模板文件,其中包含编辑器的 HTML 结构和必要的脚本引入。 ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Editor</title> <link rel="stylesheet" href="{{ url_for('static', filename='editor.md/css/editormd.css') }}"> </head> <body> <div id="editor"> <textarea></textarea> </div> <script src="{{ url_for('static', filename='editor.md/lib/jquery.min.js') }}"></script> <script src="{{ url_for('static', filename='editor.md/lib/editormd.min.js') }}"></script> <script type="text/javascript"> $(function() { var editor = editormd("editor", { // 配置选项 }); }); </script> </body> </html> ``` 在这个例子中,我们通过 Flask 的 render_template 函数将 editor.html 渲染给用户。编辑器的配置选项可以根据需求进行设置,详情可以参考 Editor.md 的官方文档。 最后,启动 Flask 程序并访问 `http://localhost:5000/editor`,你将可以看到编辑器的界面。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值