复制 与 剪切 与 粘贴

能获得选区内容后,就可以做进一步的操作啦。

剪切的话,实现原理同复制,只不过需要附加一个删除操作而已,所以先不管他。

监听事件

在浏览器中可以使用以下监听器来捕获到事件:

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 从零开始制作在线 代码编辑器相关推荐

  1. python手机代码编辑器_Python入门系列14 - 代码编辑器PyCharm篇

    Python入门系列14 代码编辑器PyCharm篇 本篇文字为2412字,阅读时间约为7分钟. 1 前言 古人云:工欲善其事必先利其器!写代码也一样,虽然好多人都说,初学者不推荐使用很高大上,智能, ...

  2. 音画制作在线html编辑器,音画帖制作(在线编辑器)

    在线编辑器,能轻松自如地编辑自己喜欢的帖子,拥有它,你就能制作出漂亮的帖子啦,本帖引用依森的音画帖制作教材,本人略作改动,在此对提供具体教材的依森表示感谢! 具体操作如下 1.先把这个带有编辑器网站地 ...

  3. php代码编辑器6,优秀的PHP代码编辑器_Blumentals Rapid PHP V13.6 免费版

    Blumentals Rapid PHP是一款优秀的PHP编辑器,它比传统的PHP编辑环境功能更多,使用也更方便. 除了支持PHP外,它还可以用来编辑HTML, XHTML, CSS 以及 JavaS ...

  4. 在线js编辑器来喽!CodeMirror页面代码编辑器

    背景介绍:在公司的开发平台使用时需要用到js代码编写输入框,大家都是在vscode之类的编辑器写好后粘贴,在用重构项目的时候想到了这里的弊端,想在页面上也可以有js 的结构可编写代码,就清晰方便很多. ...

  5. 代码编辑器揭露性格,你是哪一种?

    "告诉我,你的编辑器." "我会告诉你,你是谁." 声明:本文作者是一个狂热的Vim用户,所以这个故事可能带有一些主观色彩. Sublime Text 喜欢用S ...

  6. 这些优秀的主流代码编辑器,你用过多少款?

    2019独角兽企业重金招聘Python工程师标准>>> 这些年来,编写代码本身已经成为一种艺术.现在,有大量的编程语言可供开发者选择使用,从汇编语言到 Ruby 和 Python.尽 ...

  7. 18款适用于开发人员的网页代码编辑器

    本文介绍18款极具价值的网页代码编辑器. 1.Codeanywhere Codeanywhere是运行在浏览器上的代码编辑器,内嵌一个强大的FTP客户端.目前支持多数流行的网页格式(HTML.PHP. ...

  8. macos安装vscode_VS Code 代码编辑器入门指南:核心组件与概念

    作者:思考问题的熊 写在前面 如果当电脑只能装一个软件还需要尽量不影响日常学习工作时,不知道你的选择会是什么.我把这个看似「荒诞」的问题理解为「All-in-One」的升级版拷问. 这个问题陪伴了我很 ...

  9. code vs 集成tfs_10大Python集成开发环境和代码编辑器

    支持Python的通用编辑器和集成开发环境 Eclipse + PyDev 类别:集成开发环境 网址:www.eclipse.org Python工具:PyDev, www.pydev.org 优点: ...

最新文章

  1. Linux 中的虚拟网络
  2. 自学了python基础英语_Python自学路线图之Python基础自学
  3. 微信小程序 -字体图标
  4. linux下memcache安装
  5. web客户端安全之跨站点请求伪造攻击
  6. win7+vs2008+windows mobile6.5.3
  7. Java的核心思想(发展方向)
  8. MMP,我说每年年会我怎么老是中不了奖,原来是这样
  9. 计算机改硬盘格式,预装win10改win7硬盘格式怎么改_win10改win7分区格式如何转换...
  10. 全民一起玩Python 之 基础篇视频教程
  11. Windows 2008 R2 标准版 ie提示 当前安全设置不允许下载该文件 解决办法
  12. LCS(最大公共子序列)问题
  13. 广东省考计算机类的比例,广东公务员考试22.4万人参加 竞争比例为19:1
  14. C语言选择题知识点整理
  15. 机电毕业设计----利用CC2530芯片开发的基于ZigBee技术的灌溉模拟系统----LED显示屏代码解释(源代码)
  16. 使用 Python 在 2 秒内评估国际象棋位置
  17. SCTF-Misc400B
  18. Python集合(附练习题)
  19. 网址二维码API接口
  20. ipad如何改造成linux终端,如何实现让ipad上使用终端terminal

热门文章

  1. 在看到厄尔巴岛之前,我曾经不可一世
  2. 三、《云原生 | Kubernetes篇》helm 升级更新
  3. 《Linux进程概念,进程创建退出等待替换,环境变量等基础操作 ---总结》
  4. 第 23 章 H3C ICG(Information Communication Gateway)
  5. 【项目实战-MATLAB】:基于CNN的心音信号分类
  6. 2020C证(安全员)模拟考试题库及C证(安全员)模拟考试软件
  7. java大顶堆类,构建大顶堆、堆排序实现(java)
  8. 【数字几何处理】Deformation:Laplacian-based energyAs-rigid-as-possible 源码+介绍
  9. 外贸B2B 平台汇集
  10. 添加数量的html标签,dedecms织梦模板栏目列表中添加统计文档数量的标签