mirror of
https://github.com/purocean/yn.git
synced 2026-09-24 15:46:02 +08:00
Merge branch 'release/2.1.0'
This commit is contained in:
@@ -27,9 +27,7 @@
|
||||
+ [x] 使用 `Electron` 构建为桌面应用。不追求完全做成客户端,还是可以沿用 CS 架构。同样可以在浏览器中使用
|
||||
+ [x] 支持嵌入 `draw.io` 文档
|
||||
+ [x] 文件目录增加右键菜单
|
||||
+ [ ] 支持打开多个文件
|
||||
+ [ ] 支持使用快捷键打开常用文档
|
||||
+ [ ] 全局剪切板功能
|
||||
+ [x] 支持多标签打开多个文件
|
||||
+ [ ] 拥有更友好的配置界面
|
||||
+ [ ] 可以自定义快捷键
|
||||
|
||||
@@ -150,13 +148,16 @@ yarn run start
|
||||
|
||||
[Windows 商店下载](https://www.microsoft.com/zh-cn/p/yank-note-%e4%b8%80%e6%ac%be%e9%9d%a2%e5%90%91%e7%a8%8b%e5%ba%8f%e5%91%98%e7%9a%84-markdown-%e7%ac%94%e8%ae%b0%e5%ba%94%e7%94%a8/9n08bq8k8681?rtc=1#activetab=pivot:overviewtab)
|
||||
|
||||
### [v2.0.2](https://github.com/purocean/yn/releases/tag/v2.0.2) 2019-11-21
|
||||
1. 修复相对链接解析
|
||||
1. 图片增加背景色便于透明图片的阅读
|
||||
### [v2.1.0](https://github.com/purocean/yn/releases/tag/v2.1.0) 2019-11-21
|
||||
1. 增加多标签同时打开多个文件
|
||||
|
||||
<details>
|
||||
<summary>展开查看更多版本记录</summary>
|
||||
|
||||
### [v2.0.2](https://github.com/purocean/yn/releases/tag/v2.0.2) 2019-11-21
|
||||
1. 修复相对链接解析
|
||||
1. 图片增加背景色便于透明图片的阅读
|
||||
|
||||
### [v2.0.1](https://github.com/purocean/yn/releases/tag/v2.0.1) 2019-11-20
|
||||
1. 增加 2.0 计划
|
||||
1. Electron 打包
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"monaco-editor": "^0.17.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"socket.io-client": "^2.2.0",
|
||||
"sortablejs": "^1.10.1",
|
||||
"transliteration": "^2.1.3",
|
||||
"turndown": "^5.0.3",
|
||||
"vue": "^2.6.10",
|
||||
|
||||
@@ -132,11 +132,7 @@ export default {
|
||||
this.saveFileOpenPositionDebounce(top)
|
||||
},
|
||||
toUri (file) {
|
||||
if (file) {
|
||||
return `yank-note://${file.repo}/${file.path.replace(/^\//, '')}`
|
||||
} else {
|
||||
return 'yank-note://system/blank.md'
|
||||
}
|
||||
return File.toUri(file)
|
||||
},
|
||||
async saveFile (f = null) {
|
||||
const file = f || this.currentFile
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<Tabs :list="tabs" :value="current" @remove="removeTabs" @switch="switchTab" @change-list="setTabs"></Tabs>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import Tabs from './Tabs'
|
||||
import File from '@/lib/file'
|
||||
|
||||
const blankUri = File.toUri(null)
|
||||
|
||||
export default {
|
||||
name: 'file-tabs',
|
||||
components: { Tabs },
|
||||
data () {
|
||||
return {
|
||||
list: [],
|
||||
current: blankUri
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setTabs (list) {
|
||||
this.$store.commit('app/setTabs', list)
|
||||
},
|
||||
switchTab (item) {
|
||||
this.switchFile(item.payload.file)
|
||||
},
|
||||
removeTabs (items) {
|
||||
const keys = items.map(x => x.key)
|
||||
const tabs = this.tabs.filter(x => keys.indexOf(x.key) === -1)
|
||||
this.setTabs(tabs)
|
||||
},
|
||||
addTab (item) {
|
||||
const tab = this.tabs.find(x => item.key === x.key)
|
||||
|
||||
// 没有打开此 Tab,新建一个
|
||||
if (!tab) {
|
||||
this.setTabs(this.tabs.concat([item]))
|
||||
}
|
||||
|
||||
this.current = item.key
|
||||
},
|
||||
switchFile (file) {
|
||||
this.$store.commit('app/setCurrentFile', file)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['currentFile', 'tabs'])
|
||||
},
|
||||
watch: {
|
||||
currentFile: {
|
||||
immediate: true,
|
||||
handler (file) {
|
||||
const uri = File.toUri(file)
|
||||
const item = {
|
||||
key: uri,
|
||||
label: file ? file.name : '空白页',
|
||||
description: file ? file.path : '空白页',
|
||||
payload: { file },
|
||||
}
|
||||
|
||||
this.addTab(item)
|
||||
}
|
||||
},
|
||||
tabs (list) {
|
||||
if (list.length < 1) {
|
||||
this.addTab({
|
||||
key: blankUri,
|
||||
label: '空白页',
|
||||
description: '空白页',
|
||||
payload: { file: null }
|
||||
})
|
||||
}
|
||||
|
||||
const tab = list.find(x => x.key === this.current)
|
||||
if (!tab) {
|
||||
const currentFile = list.length > 0 ? list[list.length - 1].payload.file : null
|
||||
this.switchFile(currentFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -185,6 +185,8 @@ export default {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
|
||||
@@ -47,7 +47,7 @@ export default {
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, 0.089);
|
||||
z-index: 99999;
|
||||
padding-top: 4em;
|
||||
padding-top: 6em;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div ref="tabs" class="tabs">
|
||||
<div
|
||||
v-for="item in list"
|
||||
:key="item.key"
|
||||
:class="{tab: true, current: item.key === value}"
|
||||
:title="item.description"
|
||||
:data-key="item.key"
|
||||
@contextmenu.exact.prevent.stop="showContextMenu(item)"
|
||||
@click="switchTab(item)">
|
||||
<div class="label">{{item.label}}</div>
|
||||
<div class="close" @click.prevent.stop="removeTabs([item])">
|
||||
<y-icon class="close-icon" name="times" title="关闭"></y-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Sortable from 'sortablejs'
|
||||
import 'vue-awesome/icons/times'
|
||||
|
||||
export default {
|
||||
name: 'tabs',
|
||||
props: {
|
||||
value: String,
|
||||
list: Array,
|
||||
},
|
||||
mounted () {
|
||||
Sortable.create(this.$refs.tabs, {
|
||||
animation: 250,
|
||||
ghostClass: 'on-sort',
|
||||
direction: 'horizontal',
|
||||
onEnd: ({ oldIndex, newIndex }) => {
|
||||
this.swapTab(oldIndex, newIndex)
|
||||
}
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
showContextMenu (item) {
|
||||
this.$contextMenu.show([
|
||||
{ id: 'close', label: '关闭', onClick: () => this.removeTabs([item]) },
|
||||
{ id: 'close-other', label: '关闭其他', onClick: () => this.removeOther(item) },
|
||||
{ id: 'close-right', label: '关闭到右侧', onClick: () => this.removeRight(item) },
|
||||
{ id: 'close-left', label: '关闭到左侧', onClick: () => this.removeLeft(item) },
|
||||
{ id: 'close-all', label: '全部关闭', onClick: () => this.removeAll(item) },
|
||||
])
|
||||
},
|
||||
switchTab (item) {
|
||||
if (item.key !== this.value) {
|
||||
this.$emit('input', item.key)
|
||||
this.$emit('switch', item)
|
||||
}
|
||||
},
|
||||
removeOther (item) {
|
||||
this.removeTabs(this.list.filter(x => x.key !== item.key))
|
||||
},
|
||||
removeRight (item) {
|
||||
this.removeTabs(this.list.slice(this.list.findIndex(x => x.key === item.key) + 1))
|
||||
},
|
||||
removeLeft (item) {
|
||||
this.removeTabs(this.list.slice(0, this.list.findIndex(x => x.key === item.key)))
|
||||
},
|
||||
removeAll () {
|
||||
this.removeTabs(this.list)
|
||||
},
|
||||
removeTabs (items) {
|
||||
this.$emit('remove', items)
|
||||
},
|
||||
swapTab (oldIndex, newIndex) {
|
||||
const list = this.list
|
||||
const tmp = list[oldIndex]
|
||||
list[oldIndex] = list[newIndex]
|
||||
list[newIndex] = tmp
|
||||
this.$emit('change-list', list)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tabs {
|
||||
flex: none;
|
||||
height: 30px;
|
||||
background: #333;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.tab {
|
||||
width: 100%;
|
||||
max-width: 150px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-right: 1px;
|
||||
color: #888;
|
||||
cursor: default;
|
||||
font-size: 12px;
|
||||
background: #282828;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding: 5px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tab.current {
|
||||
color: #eee;
|
||||
background: #1d1f21;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: #999;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
color: #aaa;
|
||||
background: #444;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
zoom: .6;
|
||||
}
|
||||
|
||||
.tab.on-sort {
|
||||
background: rgb(112, 112, 112);
|
||||
}
|
||||
</style>
|
||||
@@ -174,6 +174,7 @@ export default {
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
position: relative;
|
||||
z-index: 199999;
|
||||
}
|
||||
|
||||
.resizer {
|
||||
|
||||
@@ -35,6 +35,14 @@ const extname = path => {
|
||||
return path.substr(path.lastIndexOf('.'))
|
||||
}
|
||||
|
||||
const toUri = (file) => {
|
||||
if (file && file.repo && file.path) {
|
||||
return encodeURI(`yank-note://${file.repo}/${file.path.replace(/^\//, '')}`)
|
||||
} else {
|
||||
return 'yank-note://system/blank.md'
|
||||
}
|
||||
}
|
||||
|
||||
const decrypt = (content, password) => {
|
||||
if (!password) {
|
||||
throw new Error('未输入解密密码')
|
||||
@@ -153,6 +161,7 @@ export default {
|
||||
dirname,
|
||||
basename,
|
||||
extname,
|
||||
toUri,
|
||||
decrypt,
|
||||
encrypt,
|
||||
read,
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
<StatusBar slot="footer"></StatusBar>
|
||||
<Tree slot="left"></Tree>
|
||||
<Xterm slot="terminal"></Xterm>
|
||||
<Editor
|
||||
ref="editor"
|
||||
slot="editor"
|
||||
@scroll-line="line => $refs.preview.revealLine(line)" />
|
||||
<template slot="editor">
|
||||
<FileTabs />
|
||||
<Editor ref="editor" @scroll-line="line => $refs.preview.revealLine(line)" />
|
||||
</template>
|
||||
<Preview
|
||||
ref="preview"
|
||||
slot="preview"
|
||||
@@ -23,6 +23,7 @@
|
||||
import { mapState } from 'vuex'
|
||||
import Layout from '@/components/Layout'
|
||||
import TitleBar from '@/components/TitleBar'
|
||||
import FileTabs from '@/components/FileTabs'
|
||||
import StatusBar from '@/components/StatusBar'
|
||||
import Tree from '@/components/Tree'
|
||||
import Editor from '@/components/Editor'
|
||||
@@ -33,7 +34,7 @@ import Xterm from '@/components/Xterm'
|
||||
|
||||
export default {
|
||||
name: 'x-main',
|
||||
components: { Layout, TitleBar, StatusBar, Tree, Editor, Preview, XFilter, Xterm },
|
||||
components: { Layout, TitleBar, StatusBar, Tree, Editor, Preview, XFilter, Xterm, FileTabs },
|
||||
mounted () {
|
||||
RunPlugin.clearCache()
|
||||
this.$bus.on('editor-ready', this.init)
|
||||
|
||||
@@ -44,6 +44,7 @@ export default {
|
||||
currentRepo: Storage.get('currentRepo'),
|
||||
currentFile: getLastOpenFile(Storage.get('currentRepo')),
|
||||
recentOpenTime: Storage.get('recentOpenTime', {}),
|
||||
tabs: Storage.get('tabs', []),
|
||||
passwordHash: {},
|
||||
documentInfo: {
|
||||
textLength: 0,
|
||||
@@ -77,6 +78,10 @@ export default {
|
||||
state.showSide = data
|
||||
Storage.set('showSide', data)
|
||||
},
|
||||
setTabs (state, data) {
|
||||
state.tabs = data
|
||||
Storage.set('tabs', data)
|
||||
},
|
||||
setShowXterm (state, data) {
|
||||
state.showXterm = data
|
||||
},
|
||||
|
||||
+4
-304
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 198 KiB |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yank.note.yang",
|
||||
"version": "2.0.2",
|
||||
"version": "2.1.0",
|
||||
"description": "Yank Note 一款面向程序员的 Markdown 笔记应用",
|
||||
"main": "dist/app.js",
|
||||
"license": "Apache-2.0",
|
||||
|
||||
Reference in New Issue
Block a user