Mr丶冷文

文章 分类 评论
125 10 8391

站点介绍

冷文学习者(KEVINLU98.COM),记录一个北漂小码农的日常业余生活
友链申请地址(直接评论即可): 传送门

基于codemirror打造一款markdown编辑器

MR丶冷文 2021-12-21 2065 1条评论 技术学习 markdownjavascriptcodemirror

首页 / 正文
Freewind主题v1.5版本已发布,下载请移步Freewind 1.5,同时还有主题伴生插件Freewind Markdown,下载请移步 Freewind Markdown,有问题请在留言板,交换友链请直接在友链留言,我创建了一个主题交流群,有兴趣可以加下: 点此加入
报毒我说明一下,是因为我把主题的版权信息做了加密,其中用了eval,杀毒软件认为eval函数是一个危险的操作,这点介意的话请勿下载,我也没有强迫任何人去下载,也没有向大家收取一分钱的主题费用,所以也犯不着因为这些事情来喷我,喜欢就用,不喜欢就不用,就这么简单

发布于2022-10-28

介绍

最近在开发Typecho主题Freewind的1.4版本,在开发过程中下载了一些比较优秀的Typecho主题如Joe主题,然后发现主题作者自已对Typechomarkdown编辑器是做了增强的,添加了一些自定义的功能,于是就想着自己也对做一款符合Typecho主题Freewindmarkdown编辑器,于是查看了一些资源找到了一款开源产品codemirror,然后开始基于codemirror做款封装

Codemirror官方文档:传送门

Codemirror使用

准备

npm install codemirror
import CodeMirror from 'codemirror'

创建Codemirror对象

我们可以使用如下方法创建一个cm(本文对codemirror的简称,下方同理)对象

let cm = CodeMirror.fromTextArea(dom, {}) // 两个参数:第一个参数为textarea的dom对象,第二个参数为cm的配置参数

我对我了解到的几个参数做下说明

参数名说明
mode语言模式,比如我们是markdown编辑器,我们就填写markdown,但要引入相关依赖
theme主题,除了默认主题外其它主题需要引入相关依赖
tabSizetab的长度
lineNumbers显示行号
matchTags匹配标签,需要引入相关依赖
matchBrackets括号匹配,需要引入相关依赖
lineWiseCopyCut拷贝一行
indentWithTabs开启tab键
indentUnittab键长度
autoCloseTags自动闭合标签,需要引入相关依赖
autoCloseBrackets自动闭合括号,需要引入相关依赖
autofocus自动获取焦点
styleActiveLine高亮选中行,需要引入相关依赖
scrollPastEnd在编辑器底部插入一个编辑器同等高度的空白
showReplace是否显示replace
extraKeys快捷键扩展,比较常用的ctrl+b加粗,ctrl+i 倾斜等

我构那家cm对象的代码

this.cm = CodeMirror.fromTextArea(document.getElementById(id), {
            mode: 'markdown',
            theme: 'material-palenight',
            tabSize: 4,
            lineNumbers: true, // 显示行号
            matchTags: {bothTags: true}, // 匹配标签
            matchBrackets: true, // 括号匹配
            lineWiseCopyCut: true,
            indentWithTabs: true,
            indentUnit: 4,
            lineWrapping: true,
            autoCloseTags: true,
            autoCloseBrackets: true,
            autofocus: true,
            foldGutter: true,
            keyMap: 'sublime',
            gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
            styleActiveLine: true,
            scrollPastEnd: true, // 在编辑器底部插入一个编辑器同等高度的空白
            continueComments: true,
            lint: false,
            selfContain: true,
            showReplace: false, // 是否显示replace
            highlightSelectionMatches: {
                showToken: true,
                annotateScrollbar: true,
            },
            hintOptions: {
                completeSingle: false,
                alignWithWord: false,
            }
        })

我在使用上述参数后引入的依赖

import CodeMirror from 'codemirror'

// 代码高亮
import 'codemirror/mode/markdown/markdown'

// 代码收缩
import 'codemirror/addon/fold/foldcode'
import 'codemirror/addon/fold/foldgutter'
import 'codemirror/addon/fold/markdown-fold'
import 'codemirror/addon/fold/brace-fold'
// 标签匹配与编辑配置
import 'codemirror/addon/edit/closetag'
import 'codemirror/addon/edit/closebrackets'
import 'codemirror/addon/edit/matchtags'
import 'codemirror/addon/edit/matchbrackets'
import 'codemirror/addon/edit/continuelist'
import 'codemirror/addon/edit/trailingspace'

// 快捷键
import 'codemirror/keymap/sublime'
// 搜索
import 'codemirror/addon/search/search'
import 'codemirror/addon/dialog/dialog.css'
// 其它
import 'codemirror/addon/selection/active-line'
import 'codemirror/keymap/sublime'
//主题样式
import 'codemirror/lib/codemirror.css'
import 'codemirror/theme/material-palenight.css'

//全屏
import 'codemirror/addon/display/fullscreen'
import 'codemirror/addon/display/fullscreen.css'

扩展快捷键

我这边新加上几个markdown常用快捷键

快捷键说明
Tab如果光标选中了任何值,整行缩进 ; 如果当前行光标左边的一个字符为空或者为tab或空格,进行缩进
Ctrl/Command+Enter判断开头是'> '还是'- '还是'1. '开头,则下行自动补全
Ctrl/Command+B加粗
Ctrl/Command+I倾斜
Ctrl/Command+D删除线
Ctrl/Command+.添加引用

我这边扩展快捷键的代码

extraKeys: {
  Tab: (cm) => {
    /**
                     * 处理策略
                     * 如果光标选中了任何值,整行缩进
                     * 如果当前光标所在编辑窗口为markdown,正常缩进
                     * 如果当前行光标左边的一个字符为空或者为tab或空格,进行缩进
                     */
    function indent() {
      // const spaces = Array(cm.getOption('indentUnit') + 1).join(' ')
      cm.replaceSelection('\t', 'end', '+input')
    }

    if (cm.somethingSelected()) {
      // 光标选中文本
      cm.indentSelection('add') // 整行缩进
    } else {
      const cursor = cm.getCursor() // 获取焦点
      const line = cursor.line // 获取光标所在行数
      const ch = cursor.ch // 获取光标位置
      if (ch === 0 || cm.getOption('mode') === 'text/md-mix') {
        indent() // 为markdown
      } else {
        const value = cm.getLine(line) // 获取当前行文本
        const front = value[ch - 1] // 获取光标前一字符
        switch (front) { // 为空格,tab或其他特定字符
          case '\t':
          case '<':
          case ' ':
          case "'":
          case '/':
            indent()
            return void 0
        }
        if (cm.getOption('mode') === 'text/html') {
          // 为html
          front === '>' && indent()
          try {
            cm.execCommand('emmetExpandAbbreviation') // emmet扩展
          } catch (err) {
            console.error(err)
          }
        } else {
          indent()
          // cm.showHint()
        }
      }
    }
  },
    [`${runKey}-Enter`]: (cm) => {
      // 引用,无序,有序列表延伸
      let matchStr = ''
      // 判断开头是'> '还是'- '还是'1. '开头
      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
        const selectVal = cm.getSelection()
        let preStr = cm.getRange({line: preLine, ch: 0}, head)
        let preBlank = ''
        if (/^( |\t)+/.test(preStr)) {
          preBlank = preStr.match(/^( |\t)+/)[0]
          preStr = preStr.trimLeft()
        }
        if (/^> /.test(preStr)) {
          // 以'> '开头
          matchStr = '> '
          prePos && (matchStr = `\n${preBlank}${matchStr}${selectVal}\n`) && ++preLine
          cm.replaceSelection(matchStr)
          cm.setCursor({line: preLine, ch: matchStr.length})
        } else if (/^- /.test(preStr)) {
          // 以'- '开头
          matchStr = '- '
          prePos && (matchStr = `\n${preBlank}${matchStr}${selectVal}\n`) && ++preLine
          cm.replaceSelection(matchStr)
          cm.setCursor({line: preLine, ch: matchStr.length})
        } else if (/^\d+(\.) /.test(preStr)) {
          let preNumber = 0
          if (/^\d+(\.) /.test(preStr)) {
            // 是否以'数字. '开头,找出前面的数字
            preNumber = Number.parseInt(preStr.match(/^\d+/)[0])
          }
          matchStr = `\n${preBlank}${preNumber + 1}. ${selectVal}\n`
          cm.replaceSelection(matchStr)
          cm.setCursor({line: preLine + 1, ch: matchStr.length - 2})
        }
      } 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]
          preStr = preStr.trimLeft()
        }
        if (/^> /.test(preStr)) {
          // 以'> '开头
          matchStr = '> '
          curPos && (matchStr = `\n${preBlank}${matchStr}\n`) && ++curLine
          cm.replaceSelection(matchStr)
          cm.setCursor({line: curLine, ch: matchStr.length})
        } else if (/^- /.test(preStr)) {
          // 以'- '开头
          matchStr = '- '
          curPos && (matchStr = `\n${preBlank}${matchStr}\n`) && ++curLine
          cm.replaceSelection(matchStr)
          cm.setCursor({line: curLine, ch: matchStr.length})
        } else if (/^\d+(\.) /.test(preStr)) {
          // 以'数字. '开头
          let preNumber = 0
          if (/^\d+(\.) /.test(preStr)) {
            // 是否以'数字. '开头,找出前面的数字
            preNumber = Number.parseInt(preStr.match(/^\d+/)[0])
          }
          matchStr = `\n${preBlank}${preNumber + 1}. `
          cm.replaceSelection(matchStr)
          cm.setCursor({line: curLine + 1, ch: matchStr.length - 1})
        }
      }
      cm.focus()

    },
      [`${runKey}-B`]: (cm) => {
        // 加粗
        editorTools.handleTextStyle(cm, '**')
      },
        [`${runKey}-I`]: (cm) => {
          // 倾斜
          editorTools.handleTextStyle(cm, '*')
        },
          [`${runKey}-D`]: (cm) => {
            // 删除
            editorTools.handleTextStyle(cm, '~~')
          },
            [`${runKey}-.`]: (cm) => {
              editorTools.handleUnorderedList(cm, '>')
            },
}

其中runkey为你操作系统的cmd/ctrl,获取代码为

const mac = CodeMirror.keyMap.default === CodeMirror.keyMap.macDefault
const runKey = mac ? 'Cmd' : 'Ctrl'

editorTools为一个自定义工具包,我在网上找的,然后改了一下,具体代码如下:

/**
 * Insert matching strings such as ~, **, ', etc. into both sides of the selected text to change the style
 * For code, bold, italic, and hyphen styles
 * 将匹配字符串如:~、**、`等插入选中文本两边以达到改变样式的效果
 * 用于代码,粗体,斜体和中划线样式
 * @param {Object} cm codemirror实例
 * @param {String} matchStr 匹配字符串
 */
function handleTextStyle(cm, matchStr) {
    /**
     * 已经选中文本
     * --- 获取选中的文本和前后光标位置
     * --- 判断文本前后是否有匹配字符串
     * --- 有匹配字符串
     * ------ 删除匹配字符串
     * --- 没有匹配字符串
     * ------ 插入匹配字符串
     * 没有选中文本
     * --- 获取光标位置
     * --- 判断光标前后是否有匹配字符串,处理如上
     */
    const changePos = matchStr.length
    let [preExist, aftExist] = [false, false]
    if (cm.somethingSelected()) {
        const selectInfo = cm.listSelections()[0]
        let {head, anchor} = judgePreOrAft(selectInfo)
        let {line: preLine, ch: prePos} = head
        let {line: aftLine, ch: aftPos} = anchor
        cm.getRange({line: preLine, ch: prePos - changePos}, head) === matchStr && (preExist = true)
        cm.getRange(anchor, {line: aftLine, ch: aftPos + changePos}) === matchStr && (aftExist = true)
        let preStr = preExist ? '' : matchStr
        let aftStr = aftExist ? '' : matchStr
        prePos -= preExist ? changePos : 0
        aftPos += aftExist ? changePos : 0
        const selectStr = cm.getSelection()
        cm.replaceRange(`${preStr}${selectStr}${aftStr}`, {line: preLine, ch: prePos}, {line: aftLine, ch: aftPos})
    } else {
        const cursor = cm.getCursor()
        const {line: curLine, ch: curPos} = cursor
        cm.getRange({line: curLine, ch: curPos - changePos}, cursor) === matchStr && (preExist = true)
        cm.getRange(cursor, {line: curLine, ch: curPos + changePos}) === matchStr && (aftExist = true)
        if (preExist && aftExist) {
            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 (!preExist && !aftExist) {
            cm.replaceSelection(matchStr + matchStr)
            cm.setCursor({line: curLine, ch: curPos + changePos})
        } else {
            cm.replaceRange()
        }
    }
    cm.focus()
}


/**
 * Adding an Ordered List
 * 添加有序列表
 * @param {Object} cm codemirror实例
 */
function handleOrderList(cm) {
    /**
     * 已经选中文本
     * --- 获取选中的文本和前后光标位置
     * --- 选中了多行文本
     * ------ 在每行前加上数字列表
     * --- 选中单行文本
     * ------ 检查文本前是否含有tab缩进
     * ------ 检查文本是否已‘数字.’开头
     * ------ 在开头加上数字列表
     * 没有选中文本
     * --- 获取光标位置
     * --- 检查文本前是否含有tab缩进
     * --- 检查文本是否已‘数字.’开头
     * --- 在开头加上数字列表
     */
    if (cm.somethingSelected()) {
        const selectInfo = cm.listSelections()[0]
        let {head, anchor} = judgePreOrAft(selectInfo)
        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 + getLine(i)).length
                }
            }
            cm.setCursor({line: aftLine, ch: pos})
        } else {
            const selectVal = cm.getSelection()
            let preStr = cm.getRange({line: preLine, ch: 0}, head)
            let preNumber = 0
            let preBlank = ''
            if (/^([ \t])+/.test(preStr)) {
                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()
        const curLine = cursor.line
        let preStr = cm.getRange({line: curLine, ch: 0}, cursor)
        let preNumber = 0
        let preBlank = ''
        if (/^([ \t])+/.test(preStr)) {
            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})
    }
    cm.focus()
}

/**
 * Add references and unordered lists
 * 添加引用和无序列表
 * @param {Object} cm codemirror实例
 * @param {String} matchStr 匹配字符串
 */
function handleUnorderedList(cm, matchStr) {
    /**
     * 已经选中文本
     * --- 获取选中的文本和前后光标位置
     * --- 选中了多行文本
     * ------ 在每行前加上匹配字符
     * --- 选中单行文本
     * ------ 检测开头是否有匹配的字符串,有就将其删除,否则插入匹配字符
     * 没有选中文本
     * --- 获取光标位置
     * --- 检查文本前是否含有tab缩进
     * --- 检查文本是否已‘数字.’开头
     * --- 插入匹配字符
     */
    if (cm.somethingSelected()) {
        const selectInfo = cm.listSelections()[0]
        let {head, anchor} = judgePreOrAft(selectInfo)
        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})
        } 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${matchStr}${selectVal}\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)) {
            preBlank = preStr.match(/^([ \t])+/)[0]
        }
        curPos && (matchStr = `\n${preBlank}${matchStr}`) && ++curLine
        cm.replaceSelection(matchStr)
        cm.setCursor({line: curLine, ch: matchStr.length - 1})
    }
    cm.focus()
}

/**
 * Add a horizontal or dividing line
 * 添加横线/分割线
 * @param {Object} cm codemirror实例
 */
function handleLine(cm) {
    /**
     * 已经选中文本
     * --- 获取选中的文本和前后光标位置
     * --- 插入横线
     * 没有选中文本
     * --- 获取光标位置
     * --- 插入横线
     */
    if (cm.somethingSelected()) {
        const selectInfo = cm.listSelections()[0]
        let head = judgePreOrAft(selectInfo).head
        let {line: preLine, ch: prePos} = head
        let replaceStr = '\n\n---\n'
        cm.replaceSelection(replaceStr)
        cm.setCursor({line: preLine, ch: prePos})
    } else {
        const cursor = cm.getCursor()
        let {line: curLine, ch: curPos} = cursor
        let replaceStr = curPos ? '\n\n---\n\n' : '\n---\n\n'
        curLine += curPos ? 4 : 3
        cm.replaceSelection(replaceStr)
        cm.setCursor({line: curLine, ch: 0})
    }
    cm.focus()
}

/**
 * Add headings at the heading levels H1,H2,H3,H4,H5,H6
 * 根据标题级别H1,H2,H3,H4,H5,H6,添加标题
 * @param {Object} cm codemirror实例
 * @param {Number} level 级别
 */
function handleTitle(cm, level) {
    /**
     * 已经选中文本
     * --- 获取选中的文本和前后光标位置
     * --- 如果选中了文字,并且起始位置为 0
     * ------ 插入标题
     * --- 选中了文字但起始不为0,判断前面是否已经有标题了
     * ------ 如果前面是以多个#开头并以一个空格结尾
     * --------- 删除标题
     * ------ 起始不为0,且前面也不存在标题的标识
     * --------- 插入标题
     * 没有选中文本
     */
    let preAppend = '#'
    for (let i = 0; i < level - 1; i++) {
        preAppend += '#'
    }
    preAppend += ' '
    if (cm.somethingSelected()) {
        const selectInfo = cm.listSelections()[0]
        const selectVal = cm.getSelection()
        let {head, anchor} = judgePreOrAft(selectInfo)
        let {line: preLine, ch: prePos} = head
        let aftLine = anchor.line
        if (preLine !== aftLine) return void 0
        if (!prePos) {
            cm.replaceRange(`${preAppend}${selectVal}\n`, head, anchor)
            // cm.setCursor({ line: preLine, ch: 0 })
            // cm.replaceSelection(`${preAppend}`)
            // cm.setCursor({ line: preLine, ch: (preAppend + selectVal).length })
            // cm.replaceSelection('\n')
        } else {
            const curLineVal = cm.getLine(preLine)
            const matchStr = curLineVal.substr(0, prePos)
            if (/^(#*) $/.test(matchStr)) {
                cm.replaceRange('', {line: preLine, ch: 0}, head)
            } else {
                cm.replaceSelection(`\n${preAppend}${selectVal}\n`)
            }
        }
    } else {
        const cursor = cm.getCursor()
        let {line: curLine, ch: curPos} = cursor // 获取光标位置
        const curLineVal = cm.getLine(curLine) // 当前行内容
        curPos && (preAppend = '\n\n' + preAppend)
        cm.setCursor({line: curLine + 1, ch: 0})
        cm.replaceSelection('\n')
        cm.setCursor(cursor)
        cm.replaceSelection(preAppend)
        cm.setCursor({line: curLine, ch: curPos})
        curLine += curPos ? 3 : 1
        if (curPos === curLineVal.length) {
            curLine -= 1
            curPos = preAppend.length
        } else {
            curPos = 0
        }
        cm.setCursor({line: curLine, ch: curPos})
    }
    cm.focus()
}

/**
 * Insert image or normal link
 * 插入图片或普通链接
 * @param {Object} cm codemirror实例
 * @param {Boolean} isPicture 是否为图片链接
 */
function handleLink(cm, tx = "", url = "", isPicture = false) {
    /**
     * 已经选中文本
     * --- 获取选中的文本和前后光标位置
     * --- 选中多行文本
     * ------ 退出
     * --- 选中单行文本
     * ------ 链接是否满足格式要求
     * ------ 是否为图片链接
     * ------ 插入链接格式
     * 没有选中文本
     * --- 是否为图片链接
     * --- 插入链接格式
     */
    if (cm.somethingSelected()) {
        const selectInfo = cm.listSelections()[0]
        // const selectVal = cm.getSelection()
        let {head, anchor} = judgePreOrAft(selectInfo)
        let {line: preLine, ch: prePos} = head
        let aftLine = anchor.line
        if (preLine !== aftLine) return void 0
        // const isLinkStr = regExpList.httpUrl.test(selectVal)
        let replaceStr = `[${tx}](${url})`
        prePos += tx.length + 3
        if (isPicture) {
            prePos += 1
            replaceStr = '!' + replaceStr
        }
        cm.replaceSelection(replaceStr)
        cm.setCursor({line: preLine, ch: prePos})
    } else {
        const cursor = cm.getCursor()
        let {line: curLine, ch: curPos} = cursor
        let replaceStr = '[]()'
        curPos += 3
        isPicture && (replaceStr = '!' + replaceStr) && (curPos += 1)
        cm.replaceSelection(replaceStr)
        cm.setCursor({line: curLine, ch: curPos})
    }
    cm.focus()
}

/**
 * Determine whether the cursor of the selected text is at the beginning or the end of the text
 * 判断选中文本的光标是在文本开头还是结尾
 * @param {Object} selectInfo
 * @returns {Object}
 */
function judgePreOrAft(selectInfo) {
    let {head, anchor} = selectInfo
    ;(head.line > anchor.line || (head.line === anchor.line && head.ch > anchor.ch)) && ([head, anchor] = [anchor, head])
    return {head, anchor}
}


export default {
    handleTextStyle,
    handleOrderList,
    handleUnorderedList,
    handleLine,
    handleTitle,
    handleLink,
}

事件

这里有一些事件可以回调,回调方法如下

this.cm.on('事件名称', (e) => {
  // todo 具体代码
  console.log(e)
})
  • changes:每次编辑器内容更改时触发
  • beforeChange:事件在更改生效前触发
  • cursorActivity:当光标或选中(内容)发生变化,或者编辑器的内容发生了更改的时候触发。
  • keyHandled:快捷键映射(key map)中的快捷键被处理(handle)后触发
  • inputRead:当用户输入或粘贴时编辑器时触发。
  • electrictInput:收到指定的electrict输入时触发
  • beforeSelectionChange:此事件在选中内容变化前触发
  • viewportChange:编辑器的视口( view port )改变(滚动,编辑或其它动作)时触发
  • gutterClick:编辑器的gutter(行号区域)点击时触发
  • focus:编辑器收到焦点时触发
  • blur:编辑器失去焦点时触发
  • scroll:编辑器滚动条滚动时触发

比如我这里使用了两个使用,代码如下

this.cm.on('scroll', () => {
  // 编辑器滚动条滚动,预览窗口也根着滚动
  if (this.watch) {
    let scroll = this.cm.getScrollInfo()
    let rate = scroll['top'] / scroll['height']
    this.viewer.scrollTop(this.viewer[0].scrollHeight * rate)
  }
})
this.cm.on('change', (e) => {
  // 保存内容到textarea
  e.save()
  // 这是我自定义的方法,生成html到预览窗口
  this.md2html()
})
md2html(watch = false) {
  let md = this.cm.getValue()
  if (watch || (md !== this.lastmd)) {
    this.lastmd = md
    let ht = marked.parse(md)
    if (this.settings['parse']) {
      ht = this.settings.parse(ht)
    }
    if (watch || (ht !== this.viewer.html() && this.watch)) {
      this.viewer.html(ht)
      this.viewer.scrollTop(this.viewer[0].scrollHeight)
    }
  }
}

常用API

  • cm.setValue(“Hello Kitty”):设置编辑器内容
  • cm.getValue():获取编辑器内容
  • cm.getLine(n):获取第n行的内容
  • cm.lineCount():获取当前行数
  • cm.lastLine():获取最后一行的行号
  • cm.isClean():boolean类型判断编译器是否是clean的
  • cm.getSelection():获取选中内容
  • cm.getSelections():返回array类型选中内容
  • cm.replaceSelection(“替换后的内容”):替换选中的内容
  • cm.getCursor():获取光标位置,返回{line,char}
  • cm.setCursor({line,char}):设置光标位置
  • cm.setOption("",""):设置编译器属性
  • cm.getOption(""):获取编译器属性
  • cm.addKeyMap("",""):添加key-map键值,该键值具有比原来键值更高的优先级
  • cm.removeKeyMap(""):移除key-map
  • cm.addOverlay(""):Enable a highlighting overlay…没试出效果
  • cm.removeOverlay(""):移除Overlay
  • cm.setSize(width,height):设置编译器大小
  • cm.scrollTo(x,y):设置scroll到position位置
  • cm.refresh():刷新编辑器
  • cm.execCommand(“命令”):执行命令

大家看上面的工具类也可以发现我这边用的比较多的是

  • cm.getSelections():返回array类型选中内容
  • cm.replaceSelection(“替换后的内容”):替换选中的内容
  • cm.getCursor():获取光标位置,返回{line,char}
  • cm.setCursor({line,char}):设置光标位置

所有代码

Fw-editor: 传送门

功能预览

预览地址: 传送门

参考

评论(1)

  1. 常瑞 游客 2021-12-27 20:58 回复

    想我这种学渣就看不懂

热门文章

最新评论

  • 1

    看看

  • eeee

    多谢大佬分享

  • asdasd

    强强强

  • asdasd

    感谢作者!

  • asdasd

    感谢!

日历

2024年11月

     12
3456789
10111213141516
17181920212223
24252627282930

文章目录

站点公告
Freewind主题v1.5版本已发布,下载请移步Freewind 1.5,同时还有主题伴生插件Freewind Markdown,下载请移步 Freewind Markdown,有问题请在留言板,交换友链请直接在友链留言,我创建了一个主题交流群,有兴趣可以加下: 点此加入
报毒我说明一下,是因为我把主题的版权信息做了加密,其中用了eval,杀毒软件认为eval函数是一个危险的操作,这点介意的话请勿下载,我也没有强迫任何人去下载,也没有向大家收取一分钱的主题费用,所以也犯不着因为这些事情来喷我,喜欢就用,不喜欢就不用,就这么简单
点击小铃铛关闭