mirror of
https://github.com/purocean/yn.git
synced 2026-09-24 15:46:02 +08:00
增加docx格式导出
This commit is contained in:
@@ -33,8 +33,9 @@
|
||||
+ 自动保存(加密文档不自动保存)
|
||||
+ 列表自动补全
|
||||
+ 粘贴剪切板里面的图片
|
||||
+ 运行PHP,nodejs,Python 代码,代码块第一行需要包含以 `--run--` 字符串,示例见下面截图,当然需要安装相关环境
|
||||
+ 开启了HTML解析,可以直接在文档里面使用 HTML 代码
|
||||
+ 运行 PHP,nodejs,Python 代码,代码块第一行需要包含以 `--run--` 字符串,示例见下面截图,当然需要安装相关环境
|
||||
+ 开启了 HTML 解析,可以直接在文档里面使用 HTML 代码
|
||||
+ docx 格式导出,需要安装 [pandoc](https://pandoc.org/)
|
||||
+ markdown 编辑器 [monaco-editor](https://github.com/Microsoft/monaco-editor)
|
||||
+ markdown 解析 [markdown-it](https://github.com/markdown-it/markdown-it)
|
||||
+ GitHub 风格样式和特性 [GitHub Flavored Markdown Spec](https://github.github.com/gfm/)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
const os = require('os')
|
||||
const fs = require('fs')
|
||||
const spawn = require("child_process").spawn
|
||||
|
||||
const convert = async (html, type) => {
|
||||
try {
|
||||
const path = os.tmpdir() + `/yn_convert_${new Date().getTime()}.${type}`
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const process = spawn('pandoc', ['-f', 'html', '-o', path])
|
||||
|
||||
process.on('close', (code) => {
|
||||
try {
|
||||
const data = fs.readFileSync(path)
|
||||
fs.unlinkSync(path)
|
||||
resolve(data)
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
|
||||
process.stdin.write(html)
|
||||
process.stdin.end()
|
||||
})
|
||||
} catch (e) {
|
||||
return e.message
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = convert
|
||||
+32
-13
@@ -7,12 +7,13 @@ const plantuml = require('node-plantuml')
|
||||
|
||||
const file = require('./file')
|
||||
const run = require('./run')
|
||||
const convert = require('./convert')
|
||||
|
||||
const result = (status = 'ok', message = '操作成功', data = null) => {
|
||||
return { status, message, data }
|
||||
}
|
||||
|
||||
const fileContent = (ctx, next) => {
|
||||
const fileContent = async (ctx, next) => {
|
||||
if (ctx.path === '/api/file') {
|
||||
if (ctx.method === 'GET') {
|
||||
ctx.body = result('ok', '获取成功', file.read(ctx.query.path).toString())
|
||||
@@ -29,11 +30,11 @@ const fileContent = (ctx, next) => {
|
||||
} else if (ctx.path === '/api/tree') {
|
||||
ctx.body = result('ok', '获取成功', file.tree())
|
||||
} else {
|
||||
next()
|
||||
await next()
|
||||
}
|
||||
}
|
||||
|
||||
const attachment = (ctx, next) => {
|
||||
const attachment = async (ctx, next) => {
|
||||
if (ctx.path.startsWith('/api/attachment')) {
|
||||
if (ctx.method === 'POST') {
|
||||
const path = ctx.request.body.fields.path
|
||||
@@ -44,11 +45,11 @@ const attachment = (ctx, next) => {
|
||||
ctx.body = file.read(ctx.query.path)
|
||||
}
|
||||
} else {
|
||||
next()
|
||||
await next()
|
||||
}
|
||||
}
|
||||
|
||||
const plantumlGen = (ctx, next) => {
|
||||
const plantumlGen = async (ctx, next) => {
|
||||
if (ctx.path.startsWith('/api/plantuml/svg')) {
|
||||
const gen = plantuml.generate(ctx.query.data, {format: 'svg'});
|
||||
|
||||
@@ -60,16 +61,28 @@ const plantumlGen = (ctx, next) => {
|
||||
ctx.type = 'image/png'
|
||||
ctx.body = gen.out
|
||||
} else {
|
||||
next()
|
||||
await next()
|
||||
}
|
||||
}
|
||||
|
||||
const runCode = (ctx, next) => {
|
||||
const runCode = async (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()
|
||||
await next()
|
||||
}
|
||||
}
|
||||
|
||||
const convertFile = async (ctx, next) => {
|
||||
if (ctx.path.startsWith('/api/convert/')) {
|
||||
const html = ctx.request.body.html
|
||||
const type = ctx.request.body.type
|
||||
|
||||
ctx.type = mime.getType(`file.${type}`)
|
||||
ctx.body = await convert(html, type)
|
||||
} else {
|
||||
await next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,11 +92,17 @@ app.use(static(
|
||||
path.join(__dirname, '../static')
|
||||
))
|
||||
|
||||
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))
|
||||
app.use(bodyParser({
|
||||
multipart: true,
|
||||
formLimit: '20mb',
|
||||
jsonLimit: '20mb',
|
||||
textLimit: '20mb'
|
||||
}))
|
||||
app.use(async (ctx, next) => await fileContent(ctx, next))
|
||||
app.use(async (ctx, next) => await attachment(ctx, next))
|
||||
app.use(async (ctx, next) => await plantumlGen(ctx, next))
|
||||
app.use(async (ctx, next) => await runCode(ctx, next))
|
||||
app.use(async (ctx, next) => await convertFile(ctx, next))
|
||||
|
||||
const port = 3000
|
||||
app.listen(port)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
const execFileSync = require("child_process").execFileSync;
|
||||
const execFileSync = require("child_process").execFileSync
|
||||
|
||||
const runCode = (language, code) => {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<template>
|
||||
<div class="view">
|
||||
<div class="convert">
|
||||
<form ref="convertForm" :action="`/api/convert/${convert.fileName}`" method="post" target="_blank">
|
||||
<input type="hidden" name="html" :value="convert.html">
|
||||
<input type="hidden" name="type" :value="convert.type">
|
||||
<!-- <button type="button" @click="convertFile('pdf')">pdf</button> -->
|
||||
<button type="button" @click="convertFile('docx')">docx</button>
|
||||
</form>
|
||||
</div>
|
||||
<div ref="outline" class="outline">
|
||||
<div style="padding: .5em;"><b>目录</b></div>
|
||||
<div class="catalog">
|
||||
@@ -34,11 +42,13 @@ import 'katex/dist/katex.min.css'
|
||||
export default {
|
||||
name: 'xview',
|
||||
props: {
|
||||
value: String
|
||||
value: String,
|
||||
fileName: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
heads: [],
|
||||
convert: {},
|
||||
markdown: Markdown({
|
||||
linkify: true,
|
||||
breaks: true,
|
||||
@@ -54,7 +64,7 @@ export default {
|
||||
}
|
||||
}).use(TaskLists).use(MermaidPlugin).use(Plantuml, {
|
||||
generateSource: umlCode => {
|
||||
return '/api/plantuml/png?data=' + encodeURIComponent(umlCode)
|
||||
return 'api/plantuml/png?data=' + encodeURIComponent(umlCode)
|
||||
}
|
||||
}).use(RunPlugin).use(katex).use(SourceLinePlugin).use(MarkdownItAttrs).use(MultimdTable, {enableMultilineRows: true})
|
||||
}
|
||||
@@ -98,6 +108,18 @@ export default {
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
convertFile (type) {
|
||||
const baseUrl = location.origin + location.pathname.substring(0, location.pathname.lastIndexOf('/')) + '/'
|
||||
|
||||
this.convert = {
|
||||
fileName: (this.fileName || '未命名') + `.${type}`,
|
||||
html: this.$refs.view.outerHTML.replace(/src="api/g, `src="${baseUrl}api`),
|
||||
type
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.$refs.convertForm.submit()
|
||||
}, 300)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -150,6 +172,13 @@ export default {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.convert {
|
||||
position: fixed;
|
||||
font-size: 14px;
|
||||
right: 3em;
|
||||
margin-top: -2.5em;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.outline {
|
||||
display: none;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
@scroll-line="syncScrollView"
|
||||
@paste-img="pasteImg"
|
||||
@save="saveFile"></Editor>
|
||||
<XView ref="view" class="view" :value="value" @sync-scroll="syncScrollEditor"></XView>
|
||||
<XView ref="view" class="view" :value="value" :file-name="fileName" @sync-scroll="syncScrollEditor"></XView>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -133,6 +133,9 @@ export default {
|
||||
computed: {
|
||||
unsaved () {
|
||||
return this.value !== this.lastSaveContent
|
||||
},
|
||||
fileName () {
|
||||
return this.file ? this.file.name : null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user