增加运行代码功能

This commit is contained in:
purocean
2018-05-29 19:44:17 +08:00
parent 710363bf00
commit d922314853
8 changed files with 117 additions and 5 deletions
+10 -3
View File
@@ -2,12 +2,14 @@
>vue + monaco-editor + markdown-it + nodejs + Koa
## 目标
1. 界面字大不费眼 :)
1. markdown 撰写
2. 图片文件保存在本地,导出 markdown 文件可简单处理离线工作
3. 支持一些流程图表绘制
3. 支持一些流程图表绘制 startuml mermaidt
3. 支持加密解密,用来保存账号等隐私文件,文件可单独设置密码
4. 不需要更多花哨功能,简单够用就行
5. 尽量少依赖三方库,也不花心思维护,杂凑在一起的功能,恰好工作即可 ^_^
6. 在文档中运行 PHP Python Node.js 代码块
## 直接使用
+ `cd backend; yarn; node main.js`
@@ -16,6 +18,8 @@
+ 删除文件/目录:`shift + 右键文件/目录`
+ 重命名文件/目录:`ctrl + 右键文件/目录`
+ 文件加密:以 `.c.md` 结尾的文件视为加密文件
+ startuml 图形需要安装 Java
+ 运行代码块第一行需要包含以 `--run--` 字符串,示例见下面截图,需要安装 PHP Python Node.js
## 开发
+ 后端: `cd backend; node main.js`
@@ -33,13 +37,16 @@
+ [x] 图片粘贴预览
+ [x] 未保存切换文件自动保存
+ [x] 密码输入优化
+ [x] nodejs静态文件
+ [x] 静态文件
+ [x] 运行 python php nodejs 块
+ [ ] 运行代码支持图表生成
+ [ ] 增加 rss 抓取
+ [ ] 认证
+ [ ] 全文查找 Ctrl + p
+ [ ] 微信结合
+ [ ] git备份
+ [ ] git 备份
+ [ ] 标签
+ [ ] 移动端展示
![截图](./screenshot2.png)
![截图](./screenshot.png)
+11
View File
@@ -6,6 +6,7 @@ const mime = require('mime')
const plantuml = require('node-plantuml')
const file = require('./file')
const run = require('./run')
const result = (status = 'ok', message = '操作成功', data = null) => {
return { status, message, data }
@@ -58,6 +59,15 @@ const plantumlGen = (ctx, next) => {
}
}
const runCode = (ctx, next) => {
if (ctx.path.startsWith('/api/run')) {
const rst = run.runCode(ctx.request.body.language, ctx.request.body.code)
ctx.body = result('ok', '运行成功', rst)
} else {
next()
}
}
const app = new Koa()
app.use(static(
@@ -68,6 +78,7 @@ app.use(bodyParser({multipart: true}))
app.use(async (ctx, next) => fileContent(ctx, next))
app.use(async (ctx, next) => attachment(ctx, next))
app.use(async (ctx, next) => plantumlGen(ctx, next))
app.use(async (ctx, next) => runCode(ctx, next))
const port = 3000
app.listen(port)
+22
View File
@@ -0,0 +1,22 @@
const execFileSync = require("child_process").execFileSync;
const runCode = (language, code) => {
try {
switch (language) {
case 'php':
return execFileSync('php', ['-r', code]).toString()
case 'python':
return execFileSync('python', ['-c', code]).toString()
case 'py':
return execFileSync('python', ['-c', code]).toString()
case 'js':
return execFileSync('node', ['-e', code]).toString()
default:
return `不支持 ${language} 语言`
}
} catch (e) {
return e.message
}
}
module.exports = { runCode }
+1 -1
View File
@@ -12,7 +12,7 @@ module.exports = {
assetsPublicPath: '/',
proxyTable: {
'/api': {
target: 'http://localhost:3000'
target: 'http://localhost:3001'
}
},
+69
View File
@@ -0,0 +1,69 @@
import CryptoJS from 'crypto-js'
const languages = ['php', 'python', 'js']
const cachePrefix = 'run_code_result_'
const RunPlugin = (md) => {
const temp = md.renderer.rules.fence.bind(md.renderer.rules)
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
const token = tokens[idx]
if (!languages.includes(token.info)) {
return temp(tokens, idx, options, env, slf)
}
const code = token.content.trim()
const firstLine = code.split(/\n/)[0].trim()
if (!firstLine.includes('--run--')) {
return temp(tokens, idx, options, env, slf)
}
const hash = CryptoJS.MD5(code).toString()
const click = `
var code = '${encodeURIComponent(code).replace(/'/g, '\\\'')}'
code = decodeURIComponent(code)
var resultDiv = this.parentElement.firstChild
resultDiv.innerHTML = '运行中……'
fetch('/api/run', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({language: '${token.info}', code: code})
}).then(res => {
res.json().then(result => {
if (result.status === 'ok') {
localStorage.setItem('${cachePrefix}${hash}', result.data)
resultDiv.innerHTML = result.data
} else {
resultDiv.innerHTML = result.message
}
}).catch(e => {
resultDiv.innerHTML = e.message
})
}).catch(e => {
resultDiv.innerHTML = e.message
})
`
const result = localStorage[`${cachePrefix}${hash}`] || ''
const resultDiv = `<div id="run-result-${hash}" style="position: relative;border-top: dashed 1px #888; padding: .5em 0; margin-top: 1.5em;"><div style="padding: .5em 0">${result}</div><button style="position: absolute; top: -.7em; height: 0; width: 0; border-left: .7em #b7b3b3 solid; border-top: .6em #dddddd00 solid; border-bottom: .6em #dddddd00 solid; border-right: 0; background: rgba(0, 0, 0, 0); cursor: pointer; outline: none" onclick="${click}"></button></div>`
const codeContent = temp(tokens, idx, options, env, slf).trim().replace(/<\/pre>$/, '')
return `${codeContent}${resultDiv}</pre>`
}
}
RunPlugin.clearCache = () => {
Object.keys(localStorage).forEach(key => {
if (key.startsWith(cachePrefix)) {
localStorage.removeItem(key)
}
})
}
export default RunPlugin
+2 -1
View File
@@ -9,6 +9,7 @@ import _ from 'lodash'
import Markdown from 'markdown-it'
import TaskLists from 'markdown-it-task-lists'
import Plantuml from 'markdown-it-plantuml'
import RunPlugin from './RunPlugin'
import Highlight from 'highlight.js'
import MermaidPlugin from './MermaidPlugin'
@@ -36,7 +37,7 @@ export default {
generateSource: umlCode => {
return '/api/plantuml/svg?data=' + encodeURIComponent(umlCode)
}
})
}).use(RunPlugin)
}
},
mounted () {
+2
View File
@@ -29,6 +29,7 @@
import Editor from '../components/Editor'
import XView from '../components/View'
import Tree from '../components/Tree'
import RunPlugin from '../components/RunPlugin'
import File from '../file'
export default {
@@ -44,6 +45,7 @@ export default {
}
},
mounted () {
RunPlugin.clearCache()
this.restartTimer()
},
beforeDestroy () {
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB