//定义返回的参数
^subCategoryDictList\[\d+\]\.id,
^subCategoryDictList\[\d+\]\.name

Asp.Net Core 使用Monaco Editor 实现代码编辑器功能
在项目中经常有代码在线编辑的需求,比如修改基于Xml的配置文件,编辑Json格式的测试数据等。我们可以使用微软开源的在线代码编辑器Monaco Editor实现这些功能。Monaco Editor是著名的VSCode的前身,项目地址:https://microsoft.github.io/monaco-editor/。本文介绍在Asp.Net Core项目中使用Monaco Editor实现代码编辑器功能。
安装
可以使用npm下载moaco-editor:
npm install monaco-editor@0.31.1
我们需要的文件在node_modules/monaco-editor/min/vs目录中,将整个vs目录拷贝到Asp.Net Core Web项目的wwwroot/lib目录下:

在Program.cs中需要启动静态文件:
app.UseStaticFiles();
在布局页面中引入如下css和js:
<link data-name="vs/editor/editor.main" rel="stylesheet" href="~/lib/vs/editor/editor.main.css" rel="external nofollow" />
<script src="~/lib/vs/loader.js"></script>
<script src="~/lib/vs/editor/editor.main.nls.js"></script>
<script src="~/lib/vs/editor/editor.main.js"></script>
基本的环境设置就完成了。
基本功能
现在可以在页面中实现编辑器的基本功能,在页面中增加一个div,作为编辑器容器:
<div id="container"></div>
然后增加编辑器的js代码:
<script>
$(document).ready(function () {
require.config({ paths: { ''vs'': ''/lib/vs'' } });
monaco.editor.create(document.getElementById(''container''), {
value: "function sayHello(){\n console.write(''Hello World'');\n}",
language: ''javascript''
});
})
</script>

设置
编辑器有多种设置,比如是否显示代码行、显示样式等等,设置完成后,可以使用updateOptions修改设置,示例代码如下:
var editor = monaco.editor.create(document.getElementById(''container''), {
value: "function sayHello(){\n console.write(''Hello World'');\n}",
language: ''javascript'',
lineNumbers: ''on'',
roundedSelection: false,
scrollBeyondLastLine: false,
readOnly: false,
theme: ''vs-dark''
});

代码比较
monaco editor的一个强大的功能是文字比较功能,可以将两段文字进行比较:
<script>
require.config({ paths: { ''vs'': ''/lib/vs'' } });
var originalModel = monaco.editor.createModel(
''删除行\n行一\n行二\n行三内容\n更多内容'',
''text/plain''
);
var modifiedModel = monaco.editor.createModel(
''\n行一\n行二\n行三\n更多内容\n增加行'',
''text/plain''
);
var diffEditor = monaco.editor.createDiffEditor(document.getElementById(''container''), {
// You can optionally disable the resizing
enableSplitViewResizing: false
});
diffEditor.setModel({
original: originalModel,
modified: modifiedModel
});
</script>
效果如下:

自定义语言
monaco editor 支持常见的几乎所有编程语言,在编辑这些语言的代码时可以高亮显示关键字,同时也支持对自定义语言的扩展。其功能非常强大,同时配置起来也比较复杂,这里只举个简单的例子,说明如何使用。
这里使用的“语言”很简单,目的是记录中国象棋的棋谱,关键字只有代表“车马炮”等棋子的大写汉语拼音,运算符只有代表向前、向后和平行移动的“++”、“--”和“==”,还有就是注释。
使用自定义语言,首先要注册这个语言的id:
monaco.languages.register({ id: ''mylang'' });
然后设置语言的Token Provider:
monaco.languages.setMonarchTokensProvider(''mylang'', getlang());
这样就可以在编辑器中使用这种自定义语言了,下面是完整的代码:
@page
@model CustomLanguageModel
@{
ViewData["Title"] = "自定义语言";
}
<h1>@ViewData["Title"]</h1>
<div id="container"></div>
<script>
var require = { paths: { vs: ''/lib/vs'' } };
</script>
@section Scripts
{
<script>
$(document).ready(function () {
monaco.languages.register({ id: ''mylang'' });
monaco.languages.setMonarchTokensProvider(''mylang'', getlang());
var sampleEditor=monaco.editor.create(document.getElementById(''container''), {
model:null
}
);
setTimeout(function(){
var model=monaco.editor.createModel(''// 炮二平五 \nPAO 2 == 5 \n//马八进七 \nMA 8 ++ 7'', ''mylang'');
sampleEditor.setModel(model);
},1);
});
function getlang(){
return {
//车马炮相士将帅兵卒
keywords: [ ''JU'', ''MA'', ''PAO'', ''XIANG'', ''SHI'', ''JIANG'', ''SHUAI'', ''BING'', ''ZU'' ],
//++ 进 --退 ==平
operators: [ ''++'', ''--'', ''=='' ],
symbols: /[=><!~?:&|+\-*\/\^%]+/,
// The main tokenizer for our languages
tokenizer: {
root: [
[/[A-Z][\w\$]*/, {cases: { ''@@keywords'': ''keyword'' }}],
{ include: ''@@whitespace'' },
[/@@symbols/, { cases: { ''@@operators'': ''operator''} } ],
[/\d./, ''number''],
],
comment: [
[/[^\/*]+/, ''comment'' ],
[/\/\*/, ''comment'', ''@@push'' ],
["\\*/", ''comment'', ''@@pop'' ],
[/[\/*]/, ''comment'' ]
],
whitespace: [
[/[ \t\r\n]+/, ''white''],
[/\/\*/, ''comment'', ''@@comment'' ],
[/\/\/.*$/, ''comment''],
],
},
};
}
</script>
}}
效果如下:

本文的示例项目已经上传到github: https://github.com/zhenl/monacoEditorDotNet
到此这篇关于Asp.Net Core 使用Monaco Editor 实现代码编辑器的文章就介绍到这了,更多相关Asp.Net Core代码编辑器内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
您可能感兴趣的文章:- monaco editor在Angular的使用详解
- Monaco Editor开发SQL代码提示编辑器实例详解
- vue使用monaco editor汉化右键菜单示例
- Monaco Editor实现sql和java代码提示实现示例
- vue2.x中monaco-editor的使用说明
- vue中实现Monaco Editor自定义提示功能
- 详解Monaco Editor中的Keybinding机制

C#解析json文件的实现代码
c# 解析 json
JSON(全称为JavaScript Object Notation) 是一种轻量级的数据交换格式。它是基于JavaScript语法标准的一个子集。 JSON采用完全独立于语言的文本格式,可以很容易在各种网络、平台和程序之间传输。JSON的语法很简单,易于人阅读和编写,同时也易于机器解析和生成。
JSON与XML的比较
◆可读性
JSON和XML的可读性相比较而言,由于XML提供辅助的标签,更加适合人阅读和理解。
◆文件大小与传输
XML允许使用方便的标签,所以文件尺寸是要比JSON大的。而且JSON源于Javascript,所以天生的主战场是Javascript与网络,在这里,JSON有着XML无法赶超的优势。
JSON语法
1. JSON 语法是 JavaScript 对象表示法语法的子集。
•数据在名称/值对中:名称是字符串,使用双引号表示。值可以是:数字(整数或浮点数),字符串(在双引号中),数组(在方括号中),对象(在花括号中),true/false/null。
•数据由逗号分隔:
•花括号保存对象:对象可以包含各种数据,包括数组。
•方括号保存数组:数字可以包含对象。
例如:
{
"employees": [
{
"firstName": "Bill",
"lastName": "Gates"
},
{
"firstName": "George",
"lastName": "Bush"
}
]
}
登录后复制
2. 如果JSON中含有转义字符,则需要转义。例如文件路径中需要使用"\\"而不是"\"。例如:{ "file":"C:\\a.txt"}。
.NET操作JSON
JSON文件读入到内存中就是字符串,.NET操作JSON就是生成与解析JSON字符串。操作JSON通常有以下几种方式:
1. 原始方式:自己按照JSON的语法格式,写代码直接操作JSON字符串。如非必要,应该很少人会走这条路,从头再来的。
2. 通用方式【★★★★★】:
这种方式是使用开源的类库Newtonsoft.Json(下载地址http://json.codeplex.com/)。下载后加入工程就能用。通常可以使用JObject, JsonReader, JsonWriter处理。这种方式最通用,也最灵活,可以随时修改不爽的地方。
(1)使用JsonReader读Json字符串:
string jsonText = @"{""input"" : ""value"", ""output"" : ""result""}";
JsonReader reader = new JsonTextReader(new StringReader(jsonText));
while (reader.Read())
{
Console.WriteLine(reader.TokenType + "\t\t" + reader.ValueType + "\t\t" + reader.Value);
}
登录后复制
(2)使用JsonWriter写字符串:
StringWriter sw = new StringWriter();
JsonWriter writer = new JsonTextWriter(sw);
writer.WriteStartObject();
writer.WritePropertyName("input");
writer.WriteValue("value");
writer.WritePropertyName("output");
writer.WriteValue("result");
writer.WriteEndObject();
writer.Flush();
string jsonText = sw.GetStringBuilder().ToString();
Console.WriteLine(jsonText);
登录后复制
(3)使用JObject读写字符串:
JObject jo = JObject.Parse(jsonText);
string[] values = jo.Properties().Select(item => item.Value.ToString()).ToArray();
登录后复制
(4)使用JsonSerializer读写对象(基于JsonWriter与JsonReader):
数组型数据
string jsonArrayText1 = "[{'a':'a1','b':'b1'},{'a':'a2','b':'b2'}]";
JArray ja = (JArray)JsonConvert.DeserializeObject(jsonArrayText1);
string ja1a = ja[1]["a"].ToString();
//或者
JObject o = (JObject)ja[1];
string oa = o["a"].ToString();
登录后复制
嵌套格式
string jsonText = "{\"beijing\":{\"zone\":\"海淀\",\"zone_en\":\"haidian\"}}";
JObject jo = (JObject)JsonConvert.DeserializeObject(jsonText);
string zone = jo["beijing"]["zone"].ToString();
string zone_en = jo["beijing"]["zone_en"].ToString();
登录后复制
自定义类Project
Project p = new Project() { Input = "stone", Output = "gold" };
JsonSerializer serializer = new JsonSerializer();
StringWriter sw = new StringWriter();
serializer.Serialize(new JsonTextWriter(sw), p);
Console.WriteLine(sw.GetStringBuilder().ToString());
StringReader sr = new StringReader(@"{""Input"":""stone"", ""Output"":""gold""}");
Project p1 = (Project)serializer.Deserialize(new JsonTextReader(sr), typeof(Project));
Console.WriteLine(p1.Input + "=>" + p1.Output);
登录后复制
上面的代码都是基于下面这个Project类定义:
class Project
{
public string Input { get; set; }
public string Output { get; set; }
}
登录后复制
此外,如果上面的JsonTextReader等类编译不过的话,说明是我们自己修改过的类,换成你们自己的相关类就可以了,不影响使用。
3. 内置方式:使用.NET Framework 3.5/4.0中提供的System.Web.Script.Serialization命名空间下的JavaScriptSerializer类进行对象的序列化与反序列化,很直接。
Project p = new Project() { Input = "stone", Output = "gold" };
JavaScriptSerializer serializer = new JavaScriptSerializer();
var json = serializer.Serialize(p);
Console.WriteLine(json);
var p1 = serializer.Deserialize<Project>(json);
Console.WriteLine(p1.Input + "=>" + p1.Output);
Console.WriteLine(ReferenceEquals(p,p1));
登录后复制
注意:如果使用的是VS2010,则要求当前的工程的Target Framework要改成.Net Framework 4,不能使用Client Profile。当然这个System.Web.Extensions.dll主要是Web使用的,直接在Console工程中用感觉有点浪费资源。
此外,从最后一句也可以看到,序列化与反序列化是深拷贝的一种典型的实现方式。
4. 契约方式:使用System.Runtime.Serialization.dll提供的DataContractJsonSerializer或者 JsonReaderWriterFactory实现。
Project p = new Project() { Input = "stone", Output = "gold" };
DataContractJsonSerializer serializer = new DataContractJsonSerializer(p.GetType());
string jsonText;
using (MemoryStream stream = new MemoryStream())
{
serializer.WriteObject(stream, p);
jsonText = Encoding.UTF8.GetString(stream.ToArray());
Console.WriteLine(jsonText);
}
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonText)))
{
DataContractJsonSerializer serializer1 = new DataContractJsonSerializer(typeof(Project));
Project p1 = (Project)serializer1.ReadObject(ms);
Console.WriteLine(p1.Input + "=>" + p1.Output);
}
登录后复制
这里要注意,这里的Project类和成员要加相关的Attribute:
[DataContract]
class Project
{
[DataMember]
public string Input { get; set; }
[DataMember]
public string Output { get; set; }
}
登录后复制
更多C#解析json文件的实现代码相关文章请关注PHP中文网!

Electron Markdown编辑器实战:资源管理器实现
前言
公众号:【可乐前端】,期待关注交流,分享一些有意思的前端知识
平时记笔记的时候一般使用VsCode
写markdown
,不过在本地写的话上传图片就需要搭配图床来使用,不然的话这种markdown
发布到其他地方就有问题。想着能折腾就折腾的心态,干脆就自己实现一个好了。需要实现的功能如下:
资源管理器——包括文件/文件夹的:
- 接入掘金
markdown
编辑器组件并拓展,数据同步到本地 - 基于
GitHub
实现图床,配合CDN
加速
这篇文章会详细介绍第一点,话不多说,让我们立刻开始~
读取本地资源
首先刚打开编辑器的时候需要用户选择一个文件夹,可以把这个文件夹理解为后续操作的根目录。

选择文件夹这里用到了electron
的dialog
模块,使用dialog.showOpenDialog
这个api
就可以唤起本地的文件对话框,让用户选择文件或者文件夹。拿到选择的文件夹之后,需要递归获取下面的子文件夹和文件,由于我们实现的是一个markdown
编辑器,所以我在取文件的时候只取了md
文件。组装成数结构之后传给渲染进程,然后渲染进程再以树的形式渲染出来。
渲染进程给主进程发送一个事件,这个IPC通信以及搭建这个项目的脚手架在之前的文章Electron打造你自己的录屏软件介绍过,感兴趣的同学可以点进去看看,这里就不多赘述。
const ipcRenderer = window.electron.ipcRenderer
const handleSelectFolder = () => {
ipcRenderer.send(OPEN_FILE_DIALOG)
}
在主进程中监听这个事件,在用户选择完文件夹后,可以拿到文件夹的路径。然后实现一个generateTree
函数,把获取到的根目录递归处理,组装成一个树结构。
import { dialog, ipcMain } from ''electron''
import { OPEN_FILE_DIALOG, SELECTED_DIRECTORY } from ''../event''
import fs from ''fs''
import path from ''path''
export default () => {
ipcMain.on(OPEN_FILE_DIALOG, (event) => {
dialog
.showOpenDialog({
properties: [''openDirectory'']
})
.then((result) => {
if (!result.canceled) {
const directoryPath = result.filePaths[0]
const generateTree = (dir) => {
const items = fs.readdirSync(dir, { withFileTypes: true })
return items
.map((item) => {
const fullPath = path.join(dir, item.name)
if (item.isDirectory()) {
return {
title: item.name,
key: fullPath,
children: generateTree(fullPath)
}
} else if (item.isFile() && item.name.endsWith(''.md'')) {
return {
title: item.name,
key: fullPath,
isLeaf: true//后续根据这个判断是不是文件
}
}
})
.filter(Boolean)
}
let folderTreeData = generateTree(directoryPath)
folderTreeData = [
{ title: path.basename(directoryPath), key: directoryPath, children: folderTreeData }
]
event.sender.send(SELECTED_DIRECTORY, { folderTreeData, directoryPath })
}
})
.catch((err) => {
console.log(err)
})
})
}
然后渲染进程再监听SELECTED_DIRECTORY
这个事件,拿到组装好的结构配合Antd
的Tree
组件就可以很快的把一棵树渲染出来。
ipcRenderer.on(SELECTED_DIRECTORY, (event, data) => {
const { directoryPath, folderTreeData } = data
if (folderTreeData.length === 0) {
message.info(''文件夹为空,请重新选择'')
return
}
setCurrentPath(directoryPath)
setTreeData(folderTreeData)
})
// ...
<DirectoryTree
defaultExpandedKeys={[currentPath]}
rootClassName={styles.folderTree}
treeData={treeData}
onRightClick={handleRightClick}
/>

右键菜单
这里我是做了一个右键菜单,来承载文件夹树的各个操作。

Tree
组件本身也提供了右键点击事件,而配合Antd
的Dropdown
组件的右键触发就可以实现一个右键菜单。但是常规的做法来做的话就要给每一个树节点都需要被一个Dropdown
包裹组件,必然会产生一定的性能开销。这里我只用了一个Dropdown
组件,只需要动态调整被包裹组件的位置,也可以实现每一个节点的右键菜单功能。
这里树节点的右键点击操作,我们来看看它做了什么:
const [rightMenus, setRightMenus] = useState([])
const handleRightClick = ({ event, node }) => {
const overlay = rightTriggerRef.current
const { pageX, pageY } = event
overlay.style.left = `${pageX}px`
overlay.style.top = `${pageY}px`
setSelectNode(node)
setTimeout(() => {
// overlay
const event = new MouseEvent(''contextmenu'', {
bubbles: true,
cancelable: true,
view: window,
button: 2, // 2 表示右键
// 如果需要设置鼠标坐标,可以添加以下属性
clientX: pageX,
clientY: pageY
})
}
const items = [//...]
setRightMenus(items)
// 触发右键事件
overlay.dispatchEvent(event)
})
}
//...
<Dropdown menu={{ items: rightMenus }} trigger={[''contextMenu'']}>
<div ref={rightTriggerRef} style={{ position: ''absolute'' }}></div>
</Dropdown>
当右键点击树节点时,可以从event
对象中获取到鼠标的坐标位置,同时可以获取到触发的节点对象。这个时候根据鼠标的坐标位置动态设置rightTriggerRef
的位置,设置完之后再对这个div
创建并触发一个右键事件,那么Dropdown
就会被触发了。
创建
点击创建菜单项的时候会弹出一个弹窗,输入完标题点击确定之后就会真正走创建的逻辑。

const path = selectNode.key
ipcRenderer.send(action, path, title, treeData, selectNode)
点击确定之后渲染进程会向主进程发一个事件,介绍一下上面的几个参数:
selectNode
:右键菜单触发的节点action
:对应的操作——创建、删除、复制等path
:节点在文件系统的路径,同时作为节点的idtitle
:标题treeData
:当前的树数据
创建文件

主进程接收到这个事件之后,path
参数就是即将被创建的文件的父文件夹路径(因为我们只能在文件夹下创建文件)。我们可以拼出新文件的路径,然后通过fs检查一下这个文件名是否已经存在了,如果不存在的话就通过writeFile
创建。
import { sep, join } from ''path''
ipcMain.on(ADD_FILE, (event, path, title, oldData) => {
const newPath = `${path}${sep}${title}.md`
const exists = fs.existsSync(newPath)
if (exists) {
event.sender.send(COMMON_ERROR, ''文件已存在'')
return
}
fs.writeFile(newPath, '''', { encoding: ''utf8'' }, (err) => {
if (!err) {
const newData = [...oldData]
addChildNode(newData, path, { title: `${title}.md`, key: newPath, isLeaf: true })
event.sender.send(UPDATE_TREE, newData)
} else {
console.log(''err'', err)
event.sender.send(COMMON_ERROR_LOG, err)
}
})
})
注意一点writeFile
创建完之后,文件确实在文件系统存在了,但是在页面上的文件树还没有更新,我们得把新创建的节点添加到treeData
中,主要关注的是addChildNode
这个函数。
// 给定一个父节点 key,在父节点下增加子节点
export const addChildNode = (treeData, parentKey, newNode) => {
const parentNode = findNodeByKey(treeData, parentKey)
if (parentNode) {
parentNode.children.push(newNode)
} else {
console.error(''Parent node with key'', parentKey, ''not found.'')
}
}
// 给定一个节点 key 找到该节点
export const findNodeByKey = (nodes, key) => {
for (let node of nodes) {
if (node.key === key) {
return node
}
if (node.children) {
const foundNode = findNodeByKey(node.children, key)
if (foundNode) {
return foundNode
}
}
}
return null
}
上面两个辅助函数帮我们在指定的父节点下插入子节点,插入完成之后通知渲染进程更新即可。
创建文件夹

创建文件夹的流程基本一样,只不过是创建的API
从writeFile
换成了mkdir
而已,具体代码如下:
ipcMain.on(ADD_FOLDER, (event, path, title, oldData) => {
const newPath = `${path}${sep}${title}`
const exists = fs.existsSync(newPath)
if (exists) {
event.sender.send(COMMON_ERROR, ''文件夹已存在'')
return
}
fs.mkdir(newPath, {}, (err) => {
if (!err) {
const newData = [...oldData]
addChildNode(newData, path, { title: `${title}`, key: newPath, children: [] })
event.sender.send(UPDATE_TREE, newData)
} else {
console.log(''err'', err)
event.sender.send(COMMON_ERROR_LOG, err)
}
})
})
重命名

重命名操作的交互跟创建的交互是一样,主要也是关注标题就可以了。来看一下重命名具体做的事情。需要根据文件/文件夹的完整路径中找出旧名字,然后把新名字拼在完整路径上就可以。这里可以根据分割符path.sep
来分割完整路径,最后一项就是旧名字。
ipcMain.on(RENAME, (event, path, title, oldData, selectNode) => {
const arr = path.split(sep)
arr.pop()
if (selectNode.isLeaf) {
arr.push(`${title}.md`)
} else {
arr.push(title)
}
let newPath = `${sep}${join(...arr)}`
const exists = fs.existsSync(newPath)
if (exists) {
event.sender.send(COMMON_ERROR, ''文件/文件夹重名'')
return
}
fs.rename(path, newPath, (err) => {
if (err) {
console.log(''err'', err)
event.sender.send(COMMON_ERROR_LOG, err)
} else {
const newData = [...oldData]
updateNodeValue(newData, path, ''title'', selectNode.isLeaf ? `${title}.md` : title)
updateNodeValue(newData, path, ''key'', newPath)
event.sender.send(UPDATE_TREE, newData)
}
})
})
同理,更新完文件系统的名字之后,我们还需要更新界面的数据。这里实现了一个updateNodeValue
函数,来更新指定节点的某一个属性值。这里直接复用findNodeByKey
这个函数,注意的是更新完title
属性之后也需要更新key
属性。
export const updateNodeValue = (treeData, nodeKey, nodeLabel, newValue) => {
const node = findNodeByKey(treeData, nodeKey)
if (node) {
node[nodeLabel] = newValue
}
}
删除

对于删除来说,会弹一个二次确认,点击确定之后就会真正进入删除的流程。删除的时候也可以使用fs
模块封装好的api
,删除文件的时候用unlinkSync
,删除文件夹用rmSync
,因为删除文件夹需要递归删除的,即实现类似 rm -rf
的功能,所以要加上一个{ recursive: true }
参数。
const deleteFileOrFolder = (event, path, selectNode, oldData) => {
try {
if (selectNode.isLeaf) {
fs.unlinkSync(path)
} else {
fs.rmSync(path, { recursive: true })
}
const newData = [...oldData]
deleteNodeByKey(newData, path)
event.sender.send(UPDATE_TREE, newData)
} catch (err) {
console.log(''err'', err)
event.sender.send(COMMON_ERROR_LOG, err)
}
}
ipcMain.on(DELETE, deleteFileOrFolder)
删除完文件系统的时候别忘了删除界面上的,这里实现了一个deleteNodeByKey
函数。主要也是通过递归找到对应key
的节点,然后把它删除。
export const deleteNodeByKey = (treeData, nodeKey) => {
// 遍历树
for (let i = 0; i < treeData.length; i++) {
const node = treeData[i]
if (node.key === nodeKey) {
// 如果当前节点是要删除的节点,直接从树的数组中删除该节点
treeData.splice(i, 1)
// 删除成功后退出函数
return
}
if (node.children) {
// 如果当前节点有子节点,递归删除
deleteNodeByKey(node.children, nodeKey)
}
}
}
复制
复制的时候我这里的交互是把源文件/文件夹,复制到某一个文件夹下:

所以需要有一棵这样的树来选择目标文件夹,这棵树也是基于treeData
构建出来的,取的是treeData
的非叶子节点,即文件夹节点。
export const getNonLeafNodesFromArray = (treeData) => {
const nonLeafNodes = []
treeData.forEach((tree) => {
if (tree.children && tree.children.length > 0) {
// 如果有子节点,则将当前节点加入新树,并递归获取非叶子节点
nonLeafNodes.push({
...tree,
children: getNonLeafNodesFromArray(tree.children)
})
}
})
return nonLeafNodes.length > 0 ? nonLeafNodes : []
}
首先复制的时候需要判断目标文件夹下有没有跟源文件同名的,如果有,则需要把名称加上一个唯一标识,为了方便我这里使用的是时间戳。其次,如果复制的是文件夹,文件夹下的文件路径中其实会包含文件夹的路径信息,所以这里也需要同步修改一下。然后判断一下目标文件夹是不是源文件夹的子文件夹,如果是,则中断流程。最后使用fs-extra
模块的copySync
复制就好了,复制完之后调用addChildNode
给页面插入新节点。
import fsExtra from ''fs-extra''
const copy = (event, path, targetPath, selectNode, oldData) => {
let newTitle = selectNode.title
if (fs.existsSync(`${targetPath}/${newTitle}`)) {
if (selectNode.isLeaf) {
const extIndex = newTitle.lastIndexOf(''.'')
newTitle = `${selectNode.title.substring(0, extIndex)}_${Date.now()}.md`
} else {
newTitle = `${selectNode.title}_${Date.now()}`
}
}
const newNode = {
title: newTitle,
key: `${targetPath}${sep}${newTitle}`
}
if (selectNode.isLeaf) {
newNode.isLeaf = true
} else {
newNode.children = cloneDeep(selectNode.children)
//递归遍历,修改文件夹下的文件路径
dfs(newNode.children, (node) => {
node.key = node.key.replace(`${targetPath}/${selectNode.title}`, newNode.key)
})
}
if (!selectNode.isLeaf) {
// 如果目标文件夹是源文件夹的子文件夹,则不进行复制
let newDestinationDir = newNode.key
if (isSubdirectory(path, newDestinationDir)) {
event.sender.send(COMMON_ERROR, ''目标文件夹是源文件夹的子文件夹,无法复制!'')
return
}
// 使用 fs-extra 的 copy 方法复制文件夹
try {
fsExtra.copySync(path, newDestinationDir)
} catch (error) {
event.sender.send(COMMON_ERROR_LOG, error)
}
} else {
try {
const content = fs.readFileSync(path, { encoding: ''utf8'' })
fs.writeFileSync(newNode.key, content, { encoding: ''utf8'' })
} catch (error) {}
}
const newData = [...oldData]
addChildNode(newData, targetPath, newNode)
event.sender.send(UPDATE_TREE, newData)
return { data: newData, success: true }
}
ipcMain.on(COPY, copy)
移动

移动的交互跟复制一样,对于移动这个操作,我这里是这样实现的,先复制一份,然后再把源文件删除。有了上面的铺垫之后就很简单了,5
行代码搞定
ipcMain.on(MOVE, (event, path, targetPath, selectNode, oldData) => {
const copyRes = copy(event, path, targetPath, selectNode, oldData)
if (copyRes.success) {
deleteFileOrFolder(event, path, selectNode, copyRes.data)
}
})
最后
刚开始的时候我是想着找一个开源组件来着,结果没找到合适的,就自己实现了一个,做完之后自己感觉是复习了一遍文件操作和树结构操作。后面将会介绍编辑器主体接入与图床实现,如果你觉得有意思的话,点点关注点点赞吧~
我们今天的关于JSON 编辑器实现代码和好用的json编辑器的分享就到这里,谢谢您的阅读,如果想了解更多关于Ajax+Json 级联菜单实现代码_json、Asp.Net Core 使用Monaco Editor 实现代码编辑器功能、C#解析json文件的实现代码、Electron Markdown编辑器实战:资源管理器实现的相关信息,可以在本站进行搜索。