前几天突然想给自己的在线编译器加一个Markdown编辑功能,于是花了两三天敲敲打打初步实现了这个功能。

一个Markdown编辑器需要有如下常用功能:粗体

斜体

中划线

标题

链接

图片

引用

代码

有序列表

无序列表

横线

看上去想实现这些功能有点复杂,但是Codemirror提供了很多API可以更方便地修改编辑内容。CodeMirror​codemirror.net

在阐述我是如何实现这些功能前,我先将实现时用到的API列出来。cm.somethingSelected()

是否选中编辑器内的任何文本。cm.listSelections()

选中的文本信息。cm.getRange(from: {line, ch}, to: {line, ch}, ?separator: string)

在编辑器中的给定点之间获取文本。cm.replaceRange(replacement: string, from: {line, ch}, to: {line, ch}, ?origin: string)

用replacement替换给定点之间的文本 。cm.setCursor(pos: {line, ch}|number, ?ch: number, ?options: object)

设置光标位置。cm.getCursor(?start: string)

获取光标位置 。cm.setSelection(anchor: {line, ch}, ?head: {line, ch}, ?options: object)

设置一个选择范围。cm.getLine(n: integer)

获取某行文本内容。

上面的API中,cm为Codemirror实例,也就是编辑器实例。line为行数,ch为列数(该行第几个字符)。

功能实现

首先是粗体,斜体,中划线和代码,这四个功能实现的方法是相同的。

当用户触发添加粗体、斜体、中划线或代码事件时,流程如下:

如上图所示,先来说说光标没选中文本时的处理:使用cm.getCursor()找到光标位置

使用cm.getRange()判断前后是否有匹配字符串(匹配字符串代表粗体、斜体、中划线或和代码的字符串:**、*、~~和'``') 。前面或后面有匹配字符串使用cm.replaceRange()清除匹配字符串

前面或后面没有匹配字符串使用cm.replaceSelection()添加匹配字符串

具体代码和注释如下:

const changePos = matchStr.length

let preAlready = false, aftAlready = false // 前后是否已经有相应样式标识,如**,`,~等 const cursor = cm.getCursor()

const { line: curLine, ch: curPos } = cursor // 获取光标位置 // 判断前后是否有matchStr cm.getRange({ line: curLine, ch: curPos - changePos }, cursor) ===

matchStr && (preAlready = true)

cm.getRange(cursor, { line: curLine, ch: curPos + changePos }) ===

matchStr && (aftAlready = true)

// 去除前后的matchStr if (aftAlready && preAlready) {

cm.replaceRange('', cursor, { line: curLine, ch: curPos + changePos })

cm.replaceRange('', { line: curLine, ch: curPos - changePos }, cursor)

cm.setCursor({ line: curLine, ch: curPos - changePos })

} else if (!preAlready && !aftAlready) {

// 前后都没有matchStr cm.replaceSelection(matchStr + matchStr)

cm.setCursor({ line: curLine, ch: curPos + changePos})

}

cm.focus()

来看看效果:

在光标选中文本的情况下,处理过程相对来说要复杂一些:使用cm.listSelections()[0]获取第一组选中的文本,返回光标的起始位置与结束位置

判断所选文字的开头和结尾的位置,因为光标的起始位置是相对位置而不是绝对位置,也就是说当你从上到下,从左到右来选择文本的时候,光标起始位置所选文本开头,否则就是末尾。

使用cm.getRange()判断前后是否有匹配字符串前面或后面有匹配字符串使用cm.replaceRange()清除匹配字符串

前面或后面没有匹配字符串使用cm.replaceSelection()添加匹配字符串

更新光标选取位置

具体代码和注释如下:

const changePos = matchStr.length // matchStr为传入参数,可以是'**','*','~~','`'或者其他符合markdown语法的字符串 let preAlready = false,aftAlready = false

if (cm.somethingSelected()) {

// 如果选中了文本 const selectContent = cm.listSelections()[0] // 第一个选中的文本 let { anchor, head } =selectContent // 前后光标位置 head.line >= anchor.line &&head.sticky === 'before' &&([head, anchor] = [anchor, head])

let { line: preLine, ch: prePos } = head

let { line: aftLine, ch: aftPos } = anchor

// 判断前后是否有matchStr cm.getRange({ line: preLine, ch: prePos - changePos }, head) ===

matchStr && (preAlready = true)

cm.getRange(anchor, { line: aftLine, ch: aftPos + changePos }) ===

matchStr && (aftAlready = true)

// 去除前后的matchStr aftAlready &&

cm.replaceRange('', anchor, { line: aftLine, ch: aftPos + changePos })

preAlready &&

cm.replaceRange('', { line: preLine, ch: prePos - changePos }, head)

if (!preAlready && !aftAlready) {

// 前后都没有matchStr cm.setCursor(anchor)

cm.replaceSelection(matchStr)

cm.setCursor(head)

cm.replaceSelection(matchStr)

prePos += changePos

aftPos += aftLine === preLine ? changePos : 0

cm.setSelection(

{ line: aftLine, ch: aftPos },

{ line: preLine, ch: prePos }

)

} else if (!preAlready) {

// 只有后面有matchStr cm.setCursor(head)

cm.replaceSelection(matchStr)

prePos += changePos

aftPos += aftLine === preLine ? changePos : 0

cm.setSelection(

{ line: aftLine, ch: aftPos },

{ line: preLine, ch: prePos }

)

} else if (!aftAlready) {

// 只有前面有matchStr cm.setCursor({ line: aftLine, ch: aftPos - changePos })

cm.replaceSelection(matchStr)

prePos -= changePos

aftPos -= aftLine === preLine ? changePos : 0

cm.setSelection(

{ line: aftLine, ch: aftPos },

{ line: preLine, ch: prePos }

)

}

cm.focus()

}

来看看效果:

接下来我说说如何实现引用,无序列表和有序列表。

我是按照VSCode的markdown插件的机制来处理这三种格式。当用户操作引用,无序列表和有序列表时的处理流程如下:判断是否选中文本已经选中文本,找到位置已经选中多行循环将每行前面加上>、-或数字.使其变为列表项

已经选中单行将选中文本转换为列表项

没选中文本,找到光标位置该行已经是列表将列表向下延伸一行

该行不是列表无操作

具体代码和注释如下:

function addList (cm, matchStr) {

// 添加引用和无序列表, matchStr为传入参数,可以是 if (cm.somethingSelected()) {

const selectContent = cm.listSelections()[0] // 第一个选中的文本 let { anchor, head } =selectContent

head.line >= anchor.line &&head.sticky === 'before' &&([head, anchor] = [anchor, head])

let preLine = head.line

let aftLine = anchor.line

if (preLine !== aftLine) {

// 选中了多行,在每行前加上匹配字符 let pos = matchStr.length

for (let i = preLine;i <= aftLine;i++) {

cm.setCursor({ line: i, ch: 0 })

cm.replaceSelection(matchStr)

i === aftLine && (pos += cm.getLine(i).length)

}

cm.setCursor({ line: aftLine, ch: pos })

cm.focus()

} else {

// 检测开头是否有匹配的字符串,有就将其删除 const preStr = cm.getRange({ line: preLine, ch: 0 }, head)

if (preStr === matchStr) {

cm.replaceRange('', { line: preLine, ch: 0 }, head)

} else {

const selectVal = cm.getSelection()

let replaceStr = `\n\n${matchStr}${selectVal}\n\n`

cm.replaceSelection(replaceStr)

cm.setCursor({ line: preLine + 2, ch: (matchStr + selectVal).length})

}

}

} else {

const cursor = cm.getCursor()

let { line: curLine, ch: curPos } = cursor // 获取光标位置 let preStr = cm.getRange({ line: curLine, ch: 0 }, cursor)

let preBlank = ''

if (/^( |\t)+/.test(preStr)) {

// 有序列表标识前也许会有空格或tab缩进 preBlank = preStr.match(/^( |\t)+/)[0]

}

curPos && (matchStr = `\n${preBlank}${matchStr}`) && ++curLine

cm.replaceSelection(matchStr )

cm.setCursor({ line: curLine, ch: matchStr.length - 1})

}

cm.focus()

}

来看看效果:

至于有序列表,需要先去除当前行前面的空格和制表符,再判断是否以数字.开头,如果有,便取出数字 ,下一行的数字逐步递增。其他的地方和无序列表差不多。

具体代码和注释如下:

function addOrderList (cm) {

// 添加有序列表 if (cm.somethingSelected()) {

const selectContent = cm.listSelections()[0] // 第一个选中的文本 let { anchor, head } = selectContent

head.line >= anchor.line &&head.sticky === 'before' &&([head, anchor] = [anchor, head])

let preLine = head.line

let aftLine = anchor.line

if (preLine !== aftLine) {

// 选中了多行,在每行前加上匹配字符 let preNumber = 0

let pos = 0

for (let i = preLine;i <= aftLine;i++) {

cm.setCursor({ line: i, ch: 0 })

const replaceStr = `${++preNumber}. `

cm.replaceSelection(replaceStr)

if (i === aftLine) {

pos += (replaceStr + cm.getLine(i)).length

}

}

cm.setCursor({ line: aftLine, ch: pos })

cm.focus()

} else {

const selectVal = cm.getSelection()

let preStr = cm.getRange({ line: preLine, ch: 0 }, head)

let preNumber = 0

let preBlank = ''

if (/^( |\t)+/.test(preStr)) {

// 有序列表标识前也许会有空格或tab缩进 preBlank = preStr.match(/^( |\t)+/)[0]

preStr = preStr.trimLeft()

}

if (/^\d+(\.) /.test(preStr)) {

// 是否以'数字. '开头,找出前面的数字 preNumber = Number.parseInt(preStr.match(/^\d+/)[0])

}

let replaceStr = `\n${preBlank}${preNumber + 1}.${selectVal}\n`

cm.replaceSelection(replaceStr)

cm.setCursor({ line: preLine + 1, ch: replaceStr.length})

}

} else {

const cursor = cm.getCursor()

let { line: curLine, ch: curPos } = cursor // 获取光标位置 let preStr = cm.getRange({ line: curLine, ch: 0 }, cursor)

let preNumber = 0

let preBlank = ''

if (/^( |\t)+/.test(preStr)) {

// 有序列表标识前也许会有空格或tab缩进 preBlank = preStr.match(/^( |\t)+/)[0]

preStr = preStr.trimLeft()

}

if (/^\d+(\.) /.test(preStr)) {

// 是否以'数字. '开头,找出前面的数字 preNumber = Number.parseInt(preStr.match(/^\d+/)[0])

}

let replaceStr = `\n${preBlank}${preNumber + 1}. `

cm.replaceSelection(replaceStr)

cm.setCursor({ line: curLine + 1, ch: replaceStr.length - 1})

}

}

来看看效果:

如果你明白了上面的功能是怎么实现的,那么标题、链接、图片、横线的实现方法我想你也明白了。

该编辑器还没有编辑窗口和预览窗口同步滚动的功能,马克飞象的同步滚动效果我不知道该如何实现,如果有那位大神知道,望指教。马克飞象 - 专为印象笔记打造的Markdown编辑器​maxiang.io

这是该编辑器的GitHub以及项目链接Longgererer/JS-Encoder​github.comJS Encoder​www.lliiooiill.cn

进入编辑器在点击侧边栏的设置,选择预处理。

把HTML的预处理语言换成Markdown就可以开启Markdown编辑模式了。

我还是个前端小白,如果觉得那些地方需要优化和改进,望指教!

codemirror 光标定位_使用Codemirror打造Markdown编辑器相关推荐

  1. codemirror 光标定位_在线代码编辑器 CODEMIRROR 配置说明

    转自:http://www.hyjiacan.com/codemirror-config/ CodeMirror是一款在线的支持语法高亮的代码编辑器.官网: http://codemirror.net ...

  2. Markdown-VScode打造Markdown编辑器

    VScode 打造 Markdown 编辑器 在之前的博文中我分别介绍了 Markdown 的基础语法和数学公式语法,本文主要介绍使用现在非常流行的 Vscode 代码编辑器进行 Markdown 书 ...

  3. codemirror 光标定位_CodeMirror 中固定滚动位置

    CodeMirror 是最流行的代码编辑器之一,包括写作软件 WonderPen 在内的很多工具都使用它开发. 与印象笔记等笔记软件不同,WonderPen 的定位是一款写作软件.写作软件与笔记软件之 ...

  4. codemirror 光标定位_CodeMirror

    下载后,解压开到的文件夹中,lib下是放的是核心库和核心css,模式下放的是各种支持语言的语法定义,主题目录下是支持的主题样式.一般在开发中,添加lib下的引用和模式下的引用就够了. 使用示例 首先, ...

  5. codemirror 光标定位_Code Mirror Api 说明

    在JS中进行CodeMirror的一些操作绝对会使用到的api API列表: this.CodeMirrorEditor.setValue("Hello Kitty"):设置编辑器 ...

  6. 使用Atom快速打造好用的Markdown编辑器

    使用Atom快速打造好用的Markdown编辑器 Atom当前主流的跨平台的三大编辑器(Atom,sublime,vscode)之一 今天尝试了使用Atom来打造Markdown编辑器,快速上手且易用 ...

  7. md文件编辑器_可能是颜值最高的微信Markdown编辑器,用Markdown的你一定会爱上

    不论是新媒体小编还是拥有自己公众号的开发者和开源组织,一定想要一个能够快速编辑且成品美观大方的编辑器.毕竟微信自带的编辑器功能有限,市面上其他编辑器功能又过于繁多,尤其对于开发者来说,文章中插入代码块 ...

  8. 可能是颜值最高的微信Markdown编辑器,用Markdown的你一定会爱上

    不论是新媒体小编还是拥有自己公众号的开发者和开源组织,一定想要一个能够快速编辑且成品美观大方的编辑器.毕竟微信自带的编辑器功能有限,市面上其他编辑器功能又过于繁多,尤其对于开发者来说,文章中插入代码块 ...

  9. 最强markdown编辑器typora图床教程-七牛版

    typora图床教程_七牛版 markdown编辑器中的王者typora,终于支持自动将图片上传到服务器,返回url了. 终于不用先把图片拖到图床里面再上传了.泪奔. 先来看看效果吧. 注意上面图片的 ...

最新文章

  1. “腾讯电竞”向前,“腾讯游戏”向后
  2. python雷达和柱形图_Python Pygal常见数据图(折线图、柱状图、饼图、点图、仪表图和雷达图)详解...
  3. rockbox主题包安装_DUX主题
  4. leetcode746. 使用最小花费爬楼梯
  5. python用turtle调整文字位置_Python turtle学习笔记
  6. 基于heartbeatV2版本的ha-gui工具对httpd做高可用集群(1)
  7. Mac :谷歌浏览器 NET::ERR_CERT_INVALID 此证书已被撤消。网络错误和攻击行为通常是暂时的,因此,此网页稍后可能会恢复正常
  8. editview只输入英文_入门小百科丨如何在电脑/手机输入日语
  9. IT职场人生系列之十三:技术?管理?业务?
  10. Oracle管理监控之sql developer配置与简单使用
  11. C/C++:copy control (拷贝控制)
  12. Ctrl + R 后,悲剧咯、、、、
  13. java xmpp即时通讯_Android基于Xmpp的即时通讯
  14. 1080 MOOC期终成绩(25 分)
  15. 基于Java内置的HttpServer实现轻量级Restful
  16. 完整版本的 poj 题目分类 转载
  17. 关于Webgl实际中遇到的一些坑,与大家分享。
  18. G - Tiling
  19. 【uni-app】懂你找图--创建项目到首页推荐模块
  20. 阿姆斯特朗数python

热门文章

  1. 【方向盘】YourBatman在CSDN创作4周年纪念日
  2. php 超轻量级 博客系统,超轻量级个人博客 pblog | 码农软件 - 码农网
  3. mysql中Prepare、execute、deallocate的使用方法
  4. hadoop集群搭建出现的一些问题总结
  5. OrientDB单机安装教程
  6. SSM框架静态资源放行及使用——Java
  7. 机器人摘果子看图写话_小猴摘果子看图写话二年级
  8. 【Unity UGUI】简单的美术字体的制作(教你写插件)
  9. 女生做UI设计师累吗?UI设计难吗?
  10. Linux基础适合初学者