自己做在线HTML编辑器,#6 从零开始制作在线 代码编辑器
复制 与 剪切 与 粘贴
能获得选区内容后,就可以做进一步的操作啦。
剪切的话,实现原理同复制,只不过需要附加一个删除操作而已,所以先不管他。
监听事件
在浏览器中可以使用以下监听器来捕获到事件:
oncopy - 复制
oncut - 剪切
onpaste - 粘贴
剪贴板
复制/剪切后的数据会寄存在剪贴板中(clipboard)。幸运的是在浏览器中可以操作剪贴板。
在 oncopy oncut onpaste 中,可以使用 event.clipboardData 来获得剪贴板,然后使用 setData(type, content) getData(type) 来操作数据,比如有以下用法
addEventListener(self.$serval_container, 'copy', function (event) {
event.clipboardData.setData('text/plain', 'Hello, world!');
event.clipboardData.setData('text/html', 'Hello, world!');
event.preventDefault() // 阻止默认行为,避免选区内的数据覆盖掉这里写的
})
addEventListener(self.$inputer, 'paste', function (event) {
console.info(event.clipboardData.getData('text/plain'))
console.info(event.clipboardData.getData('text/html'))
event.preventDefault() // 阻止默认行为,不想要剪贴板的数据贴到编辑器中
})
执行结果(本机 )见 图6-1
图6-1.png
关于下面那行,是由于这里指定的是 text/html。在 HTML 中,Hello, world! 这样的 HTML标签 不能单独存在,所以会 自动拼接出一个最简化的完整的 HTML
这里将会用到的是 event.clipboardData.setData('text/plain', ...)
整体思路
这里从粘贴开始反推到复制,会比较容易。
假设现在剪贴板内已经有了 oncopy 提供的数据。那么在触发 onpaste 的时候,将这些数据插入到当前光标所在的行中。
另外,如果数据是有多行的情况,要在插入的时候,自动创建新的行。最后,将光标移动到插入的这些数据的末尾。
在 getSelectionContent 的时候,当时返回的是带有\n的字符串。而不是返回一个数组。这是考虑到如果在线上编辑器进行多行内容复制的时候,目的是为了与操作系统上的其他软件进行交互的话,比如把一段代码复制到QQ聊天框里,这个时候还是要手动拼接上\n换行符,所以干脆统一用字符串来传递,再通过String.prototype.split('\n') 解析成数组,之后再创建行的 DOM,最后渲染。
code
@path serval/script/harusame-serval.js
Serval.prototype._bindKeyboardEvent: function () {
/**
* 复制
* 1. 阻止复制的默认行为,手动处理复制行为
* 2. 当光标有选区的时候将选区内容放进剪贴板
*/
addEventListener(self.$serval_container, 'copy', function (event) {
event.preventDefault() /* 1 */
self.allocTask(function (v_cursor) {
if (v_cursor.isSelectionExist()) {
event.clipboardData.setData('text/plain', v_cursor.getSelectionContent()) /* 2 */
}
})
console.info('execute copy')
})
/**
* 粘贴
* 1. 阻止粘贴的默认行为,手动处理粘贴行为
* 2. 获得剪贴板的数据,只需要获得一次,所以写在外面
* 3. 分割数据成数组
* 4. 插入内容
*/
addEventListener(self.$inputer, 'paste', function (event) {
event.preventDefault() /* 1 */
var data = event.clipboardData.getData('text/plain') /* 2 */
self.allocTask(function (v_cursor) {
var data_array = data.split('\n') /* 3 */
self._insertContent(v_cursor, data_array) /* 4 */
})
console.info('execute paste')
})
},
打断一下!
可以看到在粘贴处使用了 _insertContent 函数,用来往编辑器插入内容,这个函数在前几章使用过,当时还约定了
往编辑器插入内容统一使用这个函数
结果自己回头就忘了这么回事... _(:3」∠)...
嘛... 不过最终还是要统一使用这个函数,目的是为了处理代码高亮以及撤回(Ctrl + z)操作...
因为一般编辑器插入文字的情况有两种:
插入无需换行的文字
插入 多行文字
为了调用 _insertContent 方便,会在该函数中加入判断以应对两个不同的情况:
插入无需换行的文字时候,调用时允许传入一个字符串 或者 一个 长度为1 的数组
插入多行文字的时候,约定传入一个数组
另外在该函数中,会处理行号问题以及光标的位置
这个时候写这个函数已经有点微妙的恶心了_(:3」∠)... 可以不用看
@path serval/script/harusame-serval.js
/**
* 插入内容
* 1. 缓存该光标所在的行的DOM
* 2. 缓存该行的文本内容
* 3. 取得光标之前的字符串
* 4. 取得光标之后的字符串
* 5. 拼接出完整的插入内容后的字符串
* 6. 移动游标
*/
Serval.prototype._insertContent: function (v_cursor, v_content) {
var $line = v_cursor.line.$line_content /* 1 */
var textContent = $line.textContent /* 2 */
var logicalY = v_cursor.logicalY
var logicalX = v_cursor.logicalX
var content_before = textContent.substring(0, logicalX) /* 3 */
var content_after = textContent.substring(logicalX, textContent.length) /* 4 */
var type = v_content.constructor
var array_length = v_content.length
if (type === String) {
$line.textContent = content_before + v_content + content_after /* 5 */
v_cursor.logicalX += v_content.length /* 6 */
} else if (type === Array) {
if (array_length === 1) {
$line.textContent = content_before + v_content[0] + content_after
v_cursor.logicalX += v_content[0].length
return
}
var first_line = v_content[0]
var extraX = first_line.length
$line.textContent = content_before + first_line
if (array_length > 1) {
for (var i = 1; i < array_length - 1; i++) {
Line.createLine(v_cursor.logicalY + i - 1, v_content[i])
Line.fixLineNumber(logicalY)
}
var last_line = v_content[array_length - 1]
Line.createLine(v_cursor.logicalY + array_length - 2, last_line)
Line.fixLineNumber(logicalY)
extraX = last_line.length
v_cursor.logicalY += array_length - 1
v_cursor.logicalX = extraX
} else {
Line.fixLineNumber(logicalY)
v_cursor.logicalY += array_length - 1
v_cursor.logicalX += extraX
}
$line = v_cursor.line.$line_content
$line.textContent += content_after
}
},
想了会有这种感觉的原因是因为:
Line.createLine的运作中使用选择器来得到操作对象,而这个函数本身会打乱行号,需 要重新修改行号 才能让 Line.createLine正常运作
之前考虑过使用数组来存储所有创建的行,这避免了使用选择器来得到DOM,应该会很好用,但是又想想如果会有几千几万行,存那么多DOM是不是不太好。
这里看来,避免过早进行优化 && 过早想的复杂 又一次引以为戒。这里在没有做正式的测试的情况下就改变原本的想法,算是一个惩罚吧 _(:3」∠)...
嘛... 至少是实现了功能。见 图6-2
图6-2.gif
剪切 与 删除选区内容
在这里,剪切实际上是两个操作,依次是:
复制内容
删除选区内容
复制内容已经做好了,反正也就一句话,相比来说删除选区内容就麻烦多啦~。
不过挺好的是,他很重要,因为很多地方都会用到他。做好了删除选取内容,选区这块基本就完成了~
删除选区内容
其实做到现在都是单光标的情况,逻辑上都不复杂,就加快进度了(偷懒),解释都会加在代码中,以注释的形式存在。
同样地,这里的代码完全是为了只考虑实现功能存在了,有很多地方不合理,这些问题已经记在小本子上
简短说一下:
获取选区终点之后的内容
删除 选取范围内 除了选区起点所在的行的 其他行
将选区终点之后的内容贴到起点之后
修正行号
@path serval/script/harusame-serval
/**
* 剪切
* 1. 阻止剪切的默认行为,手动处理剪切行为
* 2. 当光标有选区的时候将选区内容放进剪贴板
* 3. 删除选区内容
* 4. 清除选区
*/
addEventListener(self.$serval_container, 'cut', function (event) {
event.preventDefault() /* 1 */
self.allocTask(function (v_cursor) {
if (v_cursor.isSelectionExist()) {
event.clipboardData.setData('text/plain', v_cursor.getSelectionContent()) /* 2 */
v_cursor.deleteSelectionContent() /* 3 */
v_cursor.setSelectionBase() /* 4 */
v_cursor.updateSelection() /* 4 */
}
})
console.info('execute cut')
})
@path serval/script/harusame-cursor.js
/**
* 删除选区内容(视图方面)
* @注意保证执行了 findSelection()
* 1. 获得终点之后的内容
* 2. 当选区有多行的时候,删除除了选区起点所在的行
* 3. 将光标移到选区起点(删除光标选区的时候,光标只会在选区起点)
* 4. 将终点之后的内容贴到光标起点之后
* 5. 修正行号
*/
deleteSelectionContent: function () {
var endY = this.selection_end.logicalY
var endX = this.selection_end.logicalX
var $end = Line.getLineContentByLogicalY(endY)
var end_textContent = $end.textContent
var end_content = end_textContent.substring(endX, end_textContent.length) /* 1 */
var startY = this.selection_start.logicalY
var offsetY = endY - startY
if (offsetY === 1) { /* 2 */
Line.deleteLine(endY)
} else if (offsetY > 1) { /* 2 */
for (var i = endY; i > startY; i--) {
Line.deleteLine(i)
}
}
this.logicalY = this.selection_start.logicalY /* 3 */
this.logicalX = this.selection_start.logicalX /* 3 */
var $line = this.line.$line_content
var startX = this.selection_start.logicalX
$line.textContent = $line.textContent.substring(0, startX) + end_content /* 4 */
Line.fixLineNumber(startY) /* 5 */
},
效果见 图6-3,只要能看到选区被删除了就可以了。
图6-3.gif
关于删除选区
这里先把删除选区放一放...
在剪切中的删除选区实际上虽然能解决问题,但是是饶了很多弯强行实现的 _(:3」∠)...。删除选区同样放在优化后再做。
在有选区的情况下,一般来说输入内容会直接覆盖掉选区。
下面的一段内容都不会考虑有选区的情况
Home && End && 上下左右
Home && End 很简单,没什么好说的。
@path serval/script/harusame-serval.js
Serval.prototype.keydownHandler = {
/**
* KEY: End
*/
'35': function (event) {
this.allocTask(function (v_cursor) {
v_cursor.logicalX = v_cursor.line.$line_content.textContent.length
})
},
/**
* KEY: Home
*/
'36': function (event) {
this.allocTask(function (v_cursor) {
v_cursor.logicalX = 0
})
},
}
上下左右的话,其实有个小细节:
仅对于上(ArrowUp) 下(ArrowDown)键,使用时会记下第一次使用时的 psysicalX 值,如果之后再次使用上或下,除了改变光标的 logicalY,这次的位置会移到所说的psysicalX 处最近的地方。当使用其他按键时,会重设这个 psysicalX 见 图6-4
图6-4.gif
这个也放到优化后再说。_(:3」∠)...
@path serval/script/harusame-serval.js
Serval.prototype.keydownHandler = {
/**
* KEY: ArrowLeft
* 1. 如果光标在行首
* 1.1. 且光标在第一行
* 1.1.1. 那么什么都不做,返回
* 1.2. 光标移到上一行的末尾
* 2. 普通情况就让 x - 1
*/
'37': function (event) {
this.allocTask(function (v_cursor) {
var logicalY = v_cursor.logicalY
var logicalX = v_cursor.logicalX
/* 1 */
if (logicalX === 0) {
if (logicalY === 0) { /* 1.1 */
return /* 1.1.1 */
}
v_cursor.logicalY -= 1 /* 1.2 */
v_cursor.logicalX = v_cursor.line.$line_content.textContent.length /* 1.2 */
return
}
v_cursor.logicalX -= 1 /* 2 */
})
},
/**
* KEY: ArrowUp
* 1. 如果光标在第一行,什么都不做
* 2. 否则 y - 1
*/
'38': function (event) {
this.allocTask(function (v_cursor) {
if (v_cursor.logicalY === 0) {
return /* 1 */
}
v_cursor.logicalY -= 1 /* 2 */
})
},
/**
* KEY: ArrowRight
*/
'39': function (event) {
this.allocTask(function (v_cursor) {
var logicalY = v_cursor.logicalY
var logicalX = v_cursor.logicalX
if (logicalX === v_cursor.line.$line_content.textContent.length) {
if (logicalY === Line.max_line_number - 1) {
return
}
v_cursor.logicalY += 1
v_cursor.logicalX = 0
return
}
v_cursor.logicalX += 1
})
},
/**
* KEY: ArrowDown
*/
'40': function (event) {
this.allocTask(function (v_cursor) {
if (v_cursor.logicalY === Line.max_line_number - 1) {
return
}
v_cursor.logicalY += 1
})
},
}
效果见 图6-5:
图6-5.gif
这个甚至不够差强人意的编辑器终于要第一次优化了~
下一篇
还没有
自己做在线HTML编辑器,#6 从零开始制作在线 代码编辑器相关推荐
- python手机代码编辑器_Python入门系列14 - 代码编辑器PyCharm篇
Python入门系列14 代码编辑器PyCharm篇 本篇文字为2412字,阅读时间约为7分钟. 1 前言 古人云:工欲善其事必先利其器!写代码也一样,虽然好多人都说,初学者不推荐使用很高大上,智能, ...
- 音画制作在线html编辑器,音画帖制作(在线编辑器)
在线编辑器,能轻松自如地编辑自己喜欢的帖子,拥有它,你就能制作出漂亮的帖子啦,本帖引用依森的音画帖制作教材,本人略作改动,在此对提供具体教材的依森表示感谢! 具体操作如下 1.先把这个带有编辑器网站地 ...
- php代码编辑器6,优秀的PHP代码编辑器_Blumentals Rapid PHP V13.6 免费版
Blumentals Rapid PHP是一款优秀的PHP编辑器,它比传统的PHP编辑环境功能更多,使用也更方便. 除了支持PHP外,它还可以用来编辑HTML, XHTML, CSS 以及 JavaS ...
- 在线js编辑器来喽!CodeMirror页面代码编辑器
背景介绍:在公司的开发平台使用时需要用到js代码编写输入框,大家都是在vscode之类的编辑器写好后粘贴,在用重构项目的时候想到了这里的弊端,想在页面上也可以有js 的结构可编写代码,就清晰方便很多. ...
- 代码编辑器揭露性格,你是哪一种?
"告诉我,你的编辑器." "我会告诉你,你是谁." 声明:本文作者是一个狂热的Vim用户,所以这个故事可能带有一些主观色彩. Sublime Text 喜欢用S ...
- 这些优秀的主流代码编辑器,你用过多少款?
2019独角兽企业重金招聘Python工程师标准>>> 这些年来,编写代码本身已经成为一种艺术.现在,有大量的编程语言可供开发者选择使用,从汇编语言到 Ruby 和 Python.尽 ...
- 18款适用于开发人员的网页代码编辑器
本文介绍18款极具价值的网页代码编辑器. 1.Codeanywhere Codeanywhere是运行在浏览器上的代码编辑器,内嵌一个强大的FTP客户端.目前支持多数流行的网页格式(HTML.PHP. ...
- macos安装vscode_VS Code 代码编辑器入门指南:核心组件与概念
作者:思考问题的熊 写在前面 如果当电脑只能装一个软件还需要尽量不影响日常学习工作时,不知道你的选择会是什么.我把这个看似「荒诞」的问题理解为「All-in-One」的升级版拷问. 这个问题陪伴了我很 ...
- code vs 集成tfs_10大Python集成开发环境和代码编辑器
支持Python的通用编辑器和集成开发环境 Eclipse + PyDev 类别:集成开发环境 网址:www.eclipse.org Python工具:PyDev, www.pydev.org 优点: ...
最新文章
- Linux 中的虚拟网络
- 自学了python基础英语_Python自学路线图之Python基础自学
- 微信小程序 -字体图标
- linux下memcache安装
- web客户端安全之跨站点请求伪造攻击
- win7+vs2008+windows mobile6.5.3
- Java的核心思想(发展方向)
- MMP,我说每年年会我怎么老是中不了奖,原来是这样
- 计算机改硬盘格式,预装win10改win7硬盘格式怎么改_win10改win7分区格式如何转换...
- 全民一起玩Python 之 基础篇视频教程
- Windows 2008 R2 标准版 ie提示 当前安全设置不允许下载该文件 解决办法
- LCS(最大公共子序列)问题
- 广东省考计算机类的比例,广东公务员考试22.4万人参加 竞争比例为19:1
- C语言选择题知识点整理
- 机电毕业设计----利用CC2530芯片开发的基于ZigBee技术的灌溉模拟系统----LED显示屏代码解释(源代码)
- 使用 Python 在 2 秒内评估国际象棋位置
- SCTF-Misc400B
- Python集合(附练习题)
- 网址二维码API接口
- ipad如何改造成linux终端,如何实现让ipad上使用终端terminal
热门文章
- 在看到厄尔巴岛之前,我曾经不可一世
- 三、《云原生 | Kubernetes篇》helm 升级更新
- 《Linux进程概念,进程创建退出等待替换,环境变量等基础操作 ---总结》
- 第 23 章 H3C ICG(Information Communication Gateway)
- 【项目实战-MATLAB】:基于CNN的心音信号分类
- 2020C证(安全员)模拟考试题库及C证(安全员)模拟考试软件
- java大顶堆类,构建大顶堆、堆排序实现(java)
- 【数字几何处理】Deformation:Laplacian-based energyAs-rigid-as-possible 源码+介绍
- 外贸B2B 平台汇集
- 添加数量的html标签,dedecms织梦模板栏目列表中添加统计文档数量的标签