MarkDown在线编辑器

项目简介

要求实现一个在线markdown编辑器,支持markdown文件的在线编辑,修改,重新加载,转化为html格式等。

Pandao MarkDown编辑器

Pandao MarkDown是基于BootStrap和jquery实现的MarkDown编辑器,已经支持MarkDown在线编辑功能,我们要做的是做一个demo把Pando MarkDown的功能调用起来,形成一整套的发布,编辑,修改等流程。
Pandao MarkDown 的官网https://pandao.github.io/editor.md/
Pandao MarkDown 的项目地址https://github.com/pandao/editor.md

发布页面

实现发布页面publish.html,这个页面不做过多粘贴,只截取一小部分作示例,详细的文件在这里https://cdn.llfc.club/publish.html

  1. <link rel="stylesheet" href="editormd/css/editormd.css" />
  2. <div id="test-editor">
  3. <textarea style="display:none;">### 关于 Editor.md
  4. **Editor.md** 是一款开源的、可嵌入的 Markdown 在线编辑器(组件),基于 CodeMirror、jQuery 和 Marked 构建。
  5. </textarea>
  6. </div>
  7. <script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
  8. <script src="editormd/editormd.min.js"></script>
  9. <script type="text/javascript">
  10. $(function() {
  11. var editor = editormd("test-editor", {
  12. // width : "100%",
  13. // height : "100%",
  14. path : "editormd/lib/"
  15. });
  16. });
  17. </script>

为id为test-editor的div生成了markdown编辑器,我们可以看一下效果
https://cdn.llfc.club/1668079260340.jpg

markdown转化为html

将markdown转化为html,需要使用markdownToHTML函数,这个函数需要传入一个节点用来接收转化生成的html。
在publish.html中再添加一个按钮用来发布文章,和一个id为”test-markdown-view”的div用来接收生成的html

  1. <div class="container-fluid" id="article-publish">
  2. <div class="row">
  3. <div class="col publish-btn-parent">
  4. <button type="button" class="btn btn-primary" id="publish-post-btn">发布文章</button>
  5. <button type="button" class="btn btn-primary" id="return-index-btn">返回列表</button>
  6. </div>
  7. </div>
  8. <div class="row">
  9. <div class="col">
  10. <div id="test-markdown-view" class="">
  11. <!-- Server-side output Markdown text -->
  12. <textarea style="display:none;">### Hello world!</textarea>
  13. </div>
  14. </div>
  15. </div>
  16. </div>

接下来实现markdown转化为html的逻辑

  1. //获取md内容转化为html
  2. var testView = editormd.markdownToHTML("test-markdown-view", {
  3. markdown: $("textarea.editormd-markdown-textarea").text(),
  4. });
  5. //组装json发送给服务器
  6. let post_data = {
  7. "md-content": $("textarea.editormd-markdown-textarea").text(),
  8. "html-content": $("#test-markdown-view").prop("outerHTML"),
  9. "title": title,
  10. }
  11. $.ajax({
  12. type: "post",
  13. contentType: "application/json;charset=utf-8",
  14. data: JSON.stringify(post_data),
  15. url: "/publish-article",
  16. dataType: "json",
  17. async: false,
  18. success: function (data) {
  19. if (data.errorcode != 0) {
  20. alert(`request error , code is ${data.errorcode}`)
  21. return;
  22. }
  23. window.location.href = '/'
  24. },
  25. error: function (XMLHttpRequest, textStatus, errorThrown) {
  26. // 状态码
  27. console.log(XMLHttpRequest.status);
  28. // 状态
  29. console.log(XMLHttpRequest.readyState);
  30. // 错误信息
  31. console.log(textStatus);
  32. }
  33. });

将md转化为html放在了id为”test-markdown-view”的div下,然后组装post_data,将markdown和html内容都发送给了服务器,服务器存储起来,方便下次修改时读取。

显示文章

因为服务器已经存储了html和markdown内容,接下来显示文章只需要读取html就可以了
先实现一个获取文章内容的函数

  1. //请求单个文章的详情
  2. function GetArticleDetail(article_id) {
  3. let post_data = { 'article_id': article_id }
  4. $.ajax(
  5. {
  6. type: "post",
  7. contentType: "application/json;charset=utf-8",
  8. data: JSON.stringify(post_data),
  9. url: "/article-detail",
  10. dataType: "json",
  11. async: false,
  12. success: function (data) {
  13. if (data.errorcode != 0) {
  14. alert(`request error , code is ${data.errorcode}`)
  15. return;
  16. }
  17. console.log("get article deatail is ", data)
  18. //编译模板的里的内容
  19. var template = Handlebars.compile($('#template-article-info').html());
  20. //把后台获取到的数据student渲染到页面
  21. $('#article-info-div').html(template(data['article-detail']));
  22. $('#article-title-div>h1').html(data['article-detail'].title);
  23. $('.article-div').html(data['article-detail']['html-content'])
  24. },
  25. error: function (XMLHttpRequest, textStatus, errorThrown) {
  26. // 状态码
  27. console.log(XMLHttpRequest.status);
  28. // 状态
  29. console.log(XMLHttpRequest.readyState);
  30. // 错误信息
  31. console.log(textStatus);
  32. }
  33. }
  34. )
  35. }

在函数GetArticleDetail里我们将服务器传过来的数据通过模板编译显示在html里,并且获取了”html-content”放在$(‘.article-div’)节点下。
服务器的逻辑就不赘述了,可以见文章底部的源码链接。
文章页面article.html简要列举一段

  1. <div class="row">
  2. <div class="col" id="article-info-div">
  3. <script type="text/x-handlebars-template" id="template-article-info">
  4. <div class="row ">
  5. <div class="col article-div" id={{artid}} data-created={{created}} data-title={{title}}>
  6. </div>
  7. </div>
  8. </script>
  9. </div>
  10. </div>
  11. <script src="/static/js/jquery.min.js"></script>
  12. <script src="/static/js/handlebars.js"></script>
  13. <script src="/static/js/index.js"></script>
  14. <script>
  15. $(function () {
  16. var pathnames = window.location.pathname.split('/')
  17. if (pathnames.length < 3) {
  18. console.log("invalid url")
  19. return;
  20. }
  21. var article_id = pathnames[2]
  22. GetArticleDetail(article_id)
  23. });
  24. </script>

所以实现过后文章页面的效果大体是这个样子
https://cdn.llfc.club/1668081722889.jpg

再次编辑

对于已经发布的文章,有时候需要再次编辑,这个时候可以通过前端向服务器发送请求获取存储的markdown,编辑markdown之后生成新的html,再次发送给服务器,以达到更新文章的目的.
简单列举一下edit.html的内容

  1. <div class="container-fluid">
  2. <div class="row">
  3. <div id="test-editor" class="col">
  4. <textarea style="display:none;">### Editor.md
  5. **Editor.md**: The open source embeddable online markdown editor, based on CodeMirror & jQuery & Marked.
  6. </textarea>
  7. </div>
  8. </div>
  9. </div>
  10. <div class="container-fluid" id="article-publish">
  11. <div class="row">
  12. <div class="col publish-btn-parent">
  13. <button type="button" class="btn btn-primary" id="publish-edit-btn">更新文章</button>
  14. <button type="button" class="btn btn-primary" id="return-index-btn">返回列表</button>
  15. </div>
  16. </div>
  17. <div class="row">
  18. <div class="col">
  19. <div id="test-markdown-view" class="">
  20. <!-- Server-side output Markdown text -->
  21. <textarea style="display:none;">### Hello world!</textarea>
  22. </div>
  23. </div>
  24. </div>
  25. </div>

和public.html类似,edit.html也包含更新按钮以及id为“test-editor”的div,该div用来存储markdown内容。
id为”test-markdown-view”的div用来存储markdown转化的html内容。
我们在页面里生成editor编辑器,并且从服务器获取markdown内容写入id为”test-editor”的div里

  1. <script>
  2. $(function () {
  3. var editor = editormd("test-editor", {
  4. height: '800px',
  5. syncScrolling: "single",
  6. emoji: true,
  7. //启动本地图片上传功能
  8. imageUpload: true,
  9. watch: true,
  10. imageFormats: ["jpg", "jpeg", "gif", "png", "bmp", "webp", "zip", "rar"],
  11. path: "./editormd/lib/",
  12. imageUploadURL: "./upload.php", //文件提交请求路径
  13. saveHTMLToTextarea: true, //注意3:这个配置,方便post提交表单
  14. previewTheme: "dark",//预览主题样式 默认白色 dark为黑色
  15. });
  16. })
  17. </script>
  18. <script>
  19. $(function () {
  20. var pathnames = window.location.pathname.split('/')
  21. if (pathnames.length < 3) {
  22. console.log("invalid url")
  23. return;
  24. }
  25. var article_id = pathnames[2]
  26. GetArticleEdit(article_id)
  27. // console.log('window.location.pathname is ', window.location.pathname.split('/'))
  28. });
  29. </script>

GetArticleEdit 这个函数用来从服务器获取markdown数据拼接到div上

  1. //请求单个文章编辑页面内容
  2. function GetArticleEdit(article_id) {
  3. let post_data = { 'article_id': article_id }
  4. $.ajax(
  5. {
  6. type: "post",
  7. contentType: "application/json;charset=utf-8",
  8. data: JSON.stringify(post_data),
  9. url: "/article-detail",
  10. dataType: "json",
  11. async: false,
  12. success: function (data) {
  13. if (data.errorcode != 0) {
  14. alert(`request error , code is ${data.errorcode}`)
  15. return;
  16. }
  17. console.log("get article deatail is ", data)
  18. $("textarea.editormd-markdown-textarea").text(data['article-detail']['md-content'])
  19. $("#title-input").val(data['article-detail']['title'])
  20. $("#title-input").attr('data-artid', data['article-detail']['artid'])
  21. },
  22. error: function (XMLHttpRequest, textStatus, errorThrown) {
  23. // 状态码
  24. console.log(XMLHttpRequest.status);
  25. // 状态
  26. console.log(XMLHttpRequest.readyState);
  27. // 错误信息
  28. console.log(textStatus);
  29. }
  30. }
  31. )
  32. }

所以通过这些逻辑我们实现了编辑功能,进入编辑页面后自动将服务器存储的markdown内容加载到页面上,编辑页面基本是这个样子
https://cdn.llfc.club/1668129289749.jpg
这样就实现了在线编辑,重新发布的功能。

总结

整体来看,通过我们的重组和架构可以实现markdown文档的在线编辑和发布。
感兴趣的可以看看我的源码,源码地址如下
https://gitee.com/secondtonone1/pandaomd-pro
原文链接
https://llfc.club/articlepage?id=2HNduA4HWE8iMt06GH4uUi2cWLy

热门评论

热门文章

  1. Qt环境搭建

    喜欢(517) 浏览(24043)
  2. vscode搭建windows C++开发环境

    喜欢(596) 浏览(81089)
  3. 聊天项目(28) 分布式服务通知好友申请

    喜欢(507) 浏览(5906)
  4. 使用hexo搭建个人博客

    喜欢(533) 浏览(11588)
  5. Linux环境搭建和编码

    喜欢(594) 浏览(13193)

最新评论

  1. 利用栅栏实现同步 Dzher:作者你好!我觉得 std::thread a(write_x); std::thread b(write_y); std::thread c(read_x_then_y); std::thread d(read_y_then_x); 这个例子中的assert fail并不会发生,原子变量设定了非relaxed内存序后一个线程的原子变量被写入,那么之后的读取一定会被同步的,c和d线程中只可能同时发生一个z++未执行的情况,最终z不是1就是2了,我测试了很多次都没有assert,请问我这个观点有什么错误,谢谢!
  2. protobuf配置和使用 熊二:你可以把dll放到系统目录,也可以配置环境变量,还能把dll丢到lib里
  3. 解决博客回复区被脚本注入的问题 secondtonone1:走到现在我忽然明白一个道理,无论工作也好生活也罢,最重要的是开心,即使一份安稳的工作不能给我带来事业上的积累也要合理的舍弃,所以我还是想去做喜欢的方向。
  4. string类 WangQi888888:确实错了,应该是!isspace(sind[index]). 否则不进入循环,还是原来的字符串“some string”
  5. 答疑汇总(thread,async源码分析) Yagus:如果引用计数为0,则会执行 future 的析构进而等待任务执行完成,那么看到的输出将是 这边应该不对吧,std::future析构只在这三种情况都满足的时候才回block: 1.共享状态是std::async 创造的(类型是_Task_async_state) 2.共享状态没有ready 3.这个future是共享状态的最后一个引用 这边共享状态类型是“_Package_state”,引用计数即使为0也不应该block啊
  6. 类和对象 陈宇航:支持!!!!
  7. 构造函数 secondtonone1:构造函数是类的基础知识,要着重掌握
  8. 聊天项目(15) 客户端实现TCP管理者 lkx:已经在&QTcpSocket::readyRead 回调函数中做了处理了的。
  9. interface应用 secondtonone1:interface是万能类型,但是使用时要转换为实际类型来使用。interface丰富了go的多态特性,也降低了传统面向对象语言的耦合性。
  10. 堆排序 secondtonone1:堆排序非常实用,定时器就是这个原理制作的。
  11. visual studio配置boost库 一giao里我离giaogiao:请问是修改成这样吗:.\b2.exe toolset=MinGW
  12. 处理网络粘包问题 zyouth: //消息的长度小于头部规定的长度,说明数据未收全,则先将部分消息放到接收节点里 if (bytes_transferred < data_len) { memcpy(_recv_msg_node->_data + _recv_msg_node->_cur_len, _data + copy_len, bytes_transferred); _recv_msg_node->_cur_len += bytes_transferred; ::memset(_data, 0, MAX_LENGTH); _socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2, shared_self)); //头部处理完成 _b_head_parse = true; return; } 把_b_head_parse = true;放在_socket.async_read_some前面是不是更好
  13. 再谈单例模式 secondtonone1:是的,C++11以后返回局部static变量对象能保证线程安全了。
  14. 聊天项目(7) visualstudio配置grpc diablorrr:cmake文件得改一下 find_package(Boost REQUIRED COMPONENTS system filesystem),要加上filesystem。在target_link_libraries中也同样加上
  15. 面试题汇总(一) secondtonone1:看到网络上经常提问的go的问题,做了一下汇总,结合自己的经验给出的答案,如有纰漏,望指正批评。
  16. C++ 并发三剑客future, promise和async Yunfei:大佬您好,如果这个线程池中加入的异步任务的形参如果有右值引用,这个commit中的返回类型推导和bind绑定就会出现问题,请问实际工程中,是不是不会用到这种任务,如果用到了,应该怎么解决?
  17. 网络编程学习方法和图书推荐 Corleone:啥程度可以找工作
  18. 创建项目和编译 secondtonone1:谢谢支持
  19. Qt 对话框 Spade2077:QDialog w(); //这里是不是不需要带括号
  20. boost::asio之socket的创建和连接 项空月:发现一些错别字 :每隔vector存储  是不是是每个. asio::mutable_buffers_1 o或者    是不是多打了个o
  21. 无锁并发队列 TenThousandOne:_head  和 _tail  替换为原子变量。那里pop的逻辑,val = _data[h] 可以移到循环外面吗
  22. 聊天项目(9) redis服务搭建 pro_lin:redis线程池的析构函数,除了pop出队列,还要free掉redis连接把
  23. Qt MVC结构之QItemDelegate介绍 胡歌-此生不换:gpt, google

个人公众号

个人微信