mirror of
https://github.com/purocean/yn.git
synced 2026-09-24 15:46:02 +08:00
Merge branch 'release/1.24.0'
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# 自用 markdown 编辑器
|
||||
# Yank-Note markdown 编辑器
|
||||
>vue + monaco-editor + markdown-it + nodejs + Koa
|
||||
|
||||
[toc]{level: [2]}
|
||||
@@ -121,6 +121,11 @@
|
||||
## 更新日志
|
||||
[最新发布](https://github.com/purocean/yn/releases)
|
||||
|
||||
### [v1.24.0](https://github.com/purocean/yn/releases/tag/v1.24.0) 2019-07-31
|
||||
1. 目录树和集成终端增加拖动调整尺寸功能
|
||||
1. 调整前端代码架构
|
||||
1. 增加 2.0 计划
|
||||
|
||||
### [v1.23.0](https://github.com/purocean/yn/releases/tag/v1.23.0) 2019-07-09
|
||||
1. 增加转换所有外链图片到本地功能 `Ctrl + Alt + L`
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Yank-Note 2.0 计划
|
||||
|
||||
+ [ ] 项目更名为 Yank-Note
|
||||
+ [ ] 可以使用 WebDAV 协议作为文件通信协议
|
||||
+ [ ] 使用 Electron 构建为桌面应用
|
||||
+ [ ] 前端界面可以调整尺寸
|
||||
+ [ ] 支持打开多个文件
|
||||
+ [ ] 支持使用快捷键打开常用文档
|
||||
+ [ ] 拥有配置界面
|
||||
+ [ ] 可以自定义快捷键
|
||||
+ [ ] 暴露 api 接口可以在文档中嵌入轻量应用,以便构建一些小工具
|
||||
@@ -197,6 +197,7 @@ exports.search = (repo, str) => {
|
||||
} else if (fs.statSync(p).isFile()) {
|
||||
if (match(p, str)) {
|
||||
files.push({
|
||||
repo,
|
||||
name: x,
|
||||
path: p.replace(basePath, ''),
|
||||
type: 'file',
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "npm run build && electron ./dist/app.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.6.1",
|
||||
"electron": "^5.0.6",
|
||||
"ts-node": "^8.3.0",
|
||||
"typescript": "^3.5.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { app, BrowserWindow } from 'electron'
|
||||
|
||||
let win: BrowserWindow | null = null
|
||||
|
||||
const createWindow = () => {
|
||||
win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
// 加载index.html文件
|
||||
// win.loadFile('../../static/index.html')
|
||||
// win.loadURL('http://g.cn')
|
||||
win.loadURL('http://localhost:8066')
|
||||
|
||||
// 打开开发者工具
|
||||
win.webContents.openDevTools()
|
||||
|
||||
// 当 window 被关闭,这个事件会被触发。
|
||||
win.on('closed', () => {
|
||||
// 取消引用 window 对象,如果你的应用支持多窗口的话,
|
||||
// 通常会把多个 window 对象存放在一个数组里面,
|
||||
// 与此同时,你应该删除相应的元素。
|
||||
win = null
|
||||
})
|
||||
}
|
||||
|
||||
// Electron 会在初始化后并准备
|
||||
// 创建浏览器窗口时,调用这个函数。
|
||||
// 部分 API 在 ready 事件触发后才能使用。
|
||||
app.on('ready', createWindow)
|
||||
|
||||
// 当全部窗口关闭时退出。
|
||||
app.on('window-all-closed', () => {
|
||||
// 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
|
||||
// 否则绝大部分应用及其菜单栏会保持激活。
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
// 在macOS上,当单击dock图标并且没有其他窗口打开时,
|
||||
// 通常在应用程序中重新创建一个窗口。
|
||||
if (win === null) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2017",
|
||||
"noImplicitAny": true,
|
||||
"outDir": "./dist",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,865 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@types/node@^10.12.18":
|
||||
version "10.14.12"
|
||||
resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-10.14.12.tgz#0eec3155a46e6c4db1f27c3e588a205f767d622f"
|
||||
|
||||
"@types/node@^12.6.1":
|
||||
version "12.6.1"
|
||||
resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-12.6.1.tgz#d5544f6de0aae03eefbb63d5120f6c8be0691946"
|
||||
|
||||
ajv@^6.5.5:
|
||||
version "6.10.1"
|
||||
resolved "https://registry.npm.taobao.org/ajv/download/ajv-6.10.1.tgz#ebf8d3af22552df9dd049bfbe50cc2390e823593"
|
||||
dependencies:
|
||||
fast-deep-equal "^2.0.1"
|
||||
fast-json-stable-stringify "^2.0.0"
|
||||
json-schema-traverse "^0.4.1"
|
||||
uri-js "^4.2.2"
|
||||
|
||||
ansi-regex@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npm.taobao.org/ansi-regex/download/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
||||
|
||||
arg@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.npm.taobao.org/arg/download/arg-4.1.0.tgz#583c518199419e0037abb74062c37f8519e575f0"
|
||||
|
||||
array-find-index@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npm.taobao.org/array-find-index/download/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
|
||||
|
||||
asn1@~0.2.3:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.npm.taobao.org/asn1/download/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||
dependencies:
|
||||
safer-buffer "~2.1.0"
|
||||
|
||||
assert-plus@1.0.0, assert-plus@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npm.taobao.org/assert-plus/download/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.npm.taobao.org/asynckit/download/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||
|
||||
aws-sign2@~0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.npm.taobao.org/aws-sign2/download/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||
|
||||
aws4@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.npm.taobao.org/aws4/download/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||
|
||||
bcrypt-pbkdf@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npm.taobao.org/bcrypt-pbkdf/download/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npm.taobao.org/buffer-from/download/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
|
||||
camelcase-keys@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npm.taobao.org/camelcase-keys/download/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7"
|
||||
dependencies:
|
||||
camelcase "^2.0.0"
|
||||
map-obj "^1.0.0"
|
||||
|
||||
camelcase@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npm.taobao.org/camelcase/download/camelcase-2.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcamelcase%2Fdownload%2Fcamelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
|
||||
|
||||
caseless@~0.12.0:
|
||||
version "0.12.0"
|
||||
resolved "https://registry.npm.taobao.org/caseless/download/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
|
||||
code-point-at@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npm.taobao.org/code-point-at/download/code-point-at-1.1.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcode-point-at%2Fdownload%2Fcode-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||
|
||||
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.npm.taobao.org/combined-stream/download/combined-stream-1.0.8.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcombined-stream%2Fdownload%2Fcombined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
|
||||
dependencies:
|
||||
delayed-stream "~1.0.0"
|
||||
|
||||
concat-stream@1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.npm.taobao.org/concat-stream/download/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.2.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
|
||||
currently-unhandled@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.npm.taobao.org/currently-unhandled/download/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea"
|
||||
dependencies:
|
||||
array-find-index "^1.0.1"
|
||||
|
||||
dashdash@^1.12.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.npm.taobao.org/dashdash/download/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
debug@2.6.9, debug@^2.1.3, debug@^2.2.0:
|
||||
version "2.6.9"
|
||||
resolved "https://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.0.0:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.npm.taobao.org/debug/download/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
decamelize@^1.1.2:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npm.taobao.org/decamelize/download/decamelize-1.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdecamelize%2Fdownload%2Fdecamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
|
||||
deep-extend@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.npm.taobao.org/deep-extend/download/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npm.taobao.org/delayed-stream/download/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
|
||||
diff@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npm.taobao.org/diff/download/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npm.taobao.org/ecc-jsbn/download/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||
dependencies:
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.1.0"
|
||||
|
||||
electron-download@^4.1.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npm.taobao.org/electron-download/download/electron-download-4.1.1.tgz#02e69556705cc456e520f9e035556ed5a015ebe8"
|
||||
dependencies:
|
||||
debug "^3.0.0"
|
||||
env-paths "^1.0.0"
|
||||
fs-extra "^4.0.1"
|
||||
minimist "^1.2.0"
|
||||
nugget "^2.0.1"
|
||||
path-exists "^3.0.0"
|
||||
rc "^1.2.1"
|
||||
semver "^5.4.1"
|
||||
sumchecker "^2.0.2"
|
||||
|
||||
electron@^5.0.6:
|
||||
version "5.0.6"
|
||||
resolved "https://registry.npm.taobao.org/electron/download/electron-5.0.6.tgz#e3ca4a58b5716fceef3fb64e1fd2222dd9c51de0"
|
||||
dependencies:
|
||||
"@types/node" "^10.12.18"
|
||||
electron-download "^4.1.0"
|
||||
extract-zip "^1.0.3"
|
||||
|
||||
env-paths@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npm.taobao.org/env-paths/download/env-paths-1.0.0.tgz#4168133b42bb05c38a35b1ae4397c8298ab369e0"
|
||||
|
||||
error-ex@^1.2.0:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.npm.taobao.org/error-ex/download/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
extend@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npm.taobao.org/extend/download/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
|
||||
extract-zip@^1.0.3:
|
||||
version "1.6.7"
|
||||
resolved "https://registry.npm.taobao.org/extract-zip/download/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9"
|
||||
dependencies:
|
||||
concat-stream "1.6.2"
|
||||
debug "2.6.9"
|
||||
mkdirp "0.5.1"
|
||||
yauzl "2.4.1"
|
||||
|
||||
extsprintf@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npm.taobao.org/extsprintf/download/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||
|
||||
extsprintf@^1.2.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.npm.taobao.org/extsprintf/download/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||
|
||||
fast-deep-equal@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npm.taobao.org/fast-deep-equal/download/fast-deep-equal-2.0.1.tgz?cache=0&sync_timestamp=1562545642929&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffast-deep-equal%2Fdownload%2Ffast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||
|
||||
fast-json-stable-stringify@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npm.taobao.org/fast-json-stable-stringify/download/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
||||
|
||||
fd-slicer@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npm.taobao.org/fd-slicer/download/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
find-up@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npm.taobao.org/find-up/download/find-up-1.1.2.tgz?cache=0&sync_timestamp=1560752359985&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffind-up%2Fdownload%2Ffind-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
|
||||
dependencies:
|
||||
path-exists "^2.0.0"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.npm.taobao.org/forever-agent/download/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
|
||||
form-data@~2.3.2:
|
||||
version "2.3.3"
|
||||
resolved "https://registry.npm.taobao.org/form-data/download/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||
dependencies:
|
||||
asynckit "^0.4.0"
|
||||
combined-stream "^1.0.6"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fs-extra@^4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npm.taobao.org/fs-extra/download/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
jsonfile "^4.0.0"
|
||||
universalify "^0.1.0"
|
||||
|
||||
get-stdin@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npm.taobao.org/get-stdin/download/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
|
||||
|
||||
getpass@^0.1.1:
|
||||
version "0.1.7"
|
||||
resolved "https://registry.npm.taobao.org/getpass/download/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.npm.taobao.org/graceful-fs/download/graceful-fs-4.2.0.tgz?cache=0&sync_timestamp=1561577984726&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgraceful-fs%2Fdownload%2Fgraceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"
|
||||
|
||||
har-schema@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npm.taobao.org/har-schema/download/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||
|
||||
har-validator@~5.1.0:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.npm.taobao.org/har-validator/download/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
|
||||
dependencies:
|
||||
ajv "^6.5.5"
|
||||
har-schema "^2.0.0"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.npm.taobao.org/hosted-git-info/download/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047"
|
||||
|
||||
http-signature@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npm.taobao.org/http-signature/download/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
jsprim "^1.2.2"
|
||||
sshpk "^1.7.0"
|
||||
|
||||
indent-string@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npm.taobao.org/indent-string/download/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
|
||||
dependencies:
|
||||
repeating "^2.0.0"
|
||||
|
||||
inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
|
||||
ini@~1.3.0:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.npm.taobao.org/ini/download/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
|
||||
|
||||
is-arrayish@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.npm.taobao.org/is-arrayish/download/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
|
||||
|
||||
is-finite@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npm.taobao.org/is-finite/download/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa"
|
||||
dependencies:
|
||||
number-is-nan "^1.0.0"
|
||||
|
||||
is-fullwidth-code-point@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npm.taobao.org/is-fullwidth-code-point/download/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
|
||||
dependencies:
|
||||
number-is-nan "^1.0.0"
|
||||
|
||||
is-typedarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npm.taobao.org/is-typedarray/download/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||
|
||||
is-utf8@^0.2.0:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.npm.taobao.org/is-utf8/download/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.npm.taobao.org/isarray/download/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npm.taobao.org/isarray/download/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
||||
isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npm.taobao.org/isstream/download/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
|
||||
jsbn@~0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.npm.taobao.org/jsbn/download/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||
|
||||
json-schema-traverse@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.npm.taobao.org/json-schema-traverse/download/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
|
||||
json-schema@0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.npm.taobao.org/json-schema/download/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
|
||||
|
||||
json-stringify-safe@~5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.npm.taobao.org/json-stringify-safe/download/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
|
||||
jsonfile@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npm.taobao.org/jsonfile/download/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsprim@^1.2.2:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.npm.taobao.org/jsprim/download/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||
dependencies:
|
||||
assert-plus "1.0.0"
|
||||
extsprintf "1.3.0"
|
||||
json-schema "0.2.3"
|
||||
verror "1.10.0"
|
||||
|
||||
load-json-file@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npm.taobao.org/load-json-file/download/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
parse-json "^2.2.0"
|
||||
pify "^2.0.0"
|
||||
pinkie-promise "^2.0.0"
|
||||
strip-bom "^2.0.0"
|
||||
|
||||
loud-rejection@^1.0.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.npm.taobao.org/loud-rejection/download/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
|
||||
dependencies:
|
||||
currently-unhandled "^0.4.1"
|
||||
signal-exit "^3.0.0"
|
||||
|
||||
make-error@^1.1.1:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.npm.taobao.org/make-error/download/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
|
||||
|
||||
map-obj@^1.0.0, map-obj@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npm.taobao.org/map-obj/download/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d"
|
||||
|
||||
meow@^3.1.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.npm.taobao.org/meow/download/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
|
||||
dependencies:
|
||||
camelcase-keys "^2.0.0"
|
||||
decamelize "^1.1.2"
|
||||
loud-rejection "^1.0.0"
|
||||
map-obj "^1.0.1"
|
||||
minimist "^1.1.3"
|
||||
normalize-package-data "^2.3.4"
|
||||
object-assign "^4.0.1"
|
||||
read-pkg-up "^1.0.1"
|
||||
redent "^1.0.0"
|
||||
trim-newlines "^1.0.0"
|
||||
|
||||
mime-db@1.40.0:
|
||||
version "1.40.0"
|
||||
resolved "https://registry.npm.taobao.org/mime-db/download/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.19:
|
||||
version "2.1.24"
|
||||
resolved "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
|
||||
dependencies:
|
||||
mime-db "1.40.0"
|
||||
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.npm.taobao.org/minimist/download/minimist-0.0.8.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fminimist%2Fdownload%2Fminimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
|
||||
minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npm.taobao.org/minimist/download/minimist-1.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fminimist%2Fdownload%2Fminimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
|
||||
mkdirp@0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
||||
ms@^2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npm.taobao.org/ms/download/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
|
||||
normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
|
||||
version "2.5.0"
|
||||
resolved "https://registry.npm.taobao.org/normalize-package-data/download/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
|
||||
dependencies:
|
||||
hosted-git-info "^2.1.4"
|
||||
resolve "^1.10.0"
|
||||
semver "2 || 3 || 4 || 5"
|
||||
validate-npm-package-license "^3.0.1"
|
||||
|
||||
nugget@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npm.taobao.org/nugget/download/nugget-2.0.1.tgz#201095a487e1ad36081b3432fa3cada4f8d071b0"
|
||||
dependencies:
|
||||
debug "^2.1.3"
|
||||
minimist "^1.1.0"
|
||||
pretty-bytes "^1.0.2"
|
||||
progress-stream "^1.1.0"
|
||||
request "^2.45.0"
|
||||
single-line-log "^1.1.2"
|
||||
throttleit "0.0.2"
|
||||
|
||||
number-is-nan@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npm.taobao.org/number-is-nan/download/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
|
||||
|
||||
oauth-sign@~0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.npm.taobao.org/oauth-sign/download/oauth-sign-0.9.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Foauth-sign%2Fdownload%2Foauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||
|
||||
object-assign@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npm.taobao.org/object-assign/download/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
||||
object-keys@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.npm.taobao.org/object-keys/download/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336"
|
||||
|
||||
parse-json@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npm.taobao.org/parse-json/download/parse-json-2.2.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fparse-json%2Fdownload%2Fparse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
|
||||
dependencies:
|
||||
error-ex "^1.2.0"
|
||||
|
||||
path-exists@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npm.taobao.org/path-exists/download/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
|
||||
dependencies:
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
path-exists@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npm.taobao.org/path-exists/download/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
|
||||
|
||||
path-parse@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.npm.taobao.org/path-parse/download/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
||||
|
||||
path-type@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npm.taobao.org/path-type/download/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
|
||||
dependencies:
|
||||
graceful-fs "^4.1.2"
|
||||
pify "^2.0.0"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npm.taobao.org/pend/download/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
|
||||
performance-now@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npm.taobao.org/performance-now/download/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
|
||||
pify@^2.0.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.npm.taobao.org/pify/download/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
|
||||
pinkie-promise@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npm.taobao.org/pinkie-promise/download/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||
dependencies:
|
||||
pinkie "^2.0.0"
|
||||
|
||||
pinkie@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npm.taobao.org/pinkie/download/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||
|
||||
pretty-bytes@^1.0.2:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npm.taobao.org/pretty-bytes/download/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"
|
||||
dependencies:
|
||||
get-stdin "^4.0.1"
|
||||
meow "^3.1.0"
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npm.taobao.org/process-nextick-args/download/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
|
||||
progress-stream@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npm.taobao.org/progress-stream/download/progress-stream-1.2.0.tgz#2cd3cfea33ba3a89c9c121ec3347abe9ab125f77"
|
||||
dependencies:
|
||||
speedometer "~0.1.2"
|
||||
through2 "~0.2.3"
|
||||
|
||||
psl@^1.1.24:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npm.taobao.org/psl/download/psl-1.2.0.tgz#df12b5b1b3a30f51c329eacbdef98f3a6e136dc6"
|
||||
|
||||
punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.npm.taobao.org/punycode/download/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||
|
||||
punycode@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npm.taobao.org/punycode/download/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||
|
||||
qs@~6.5.2:
|
||||
version "6.5.2"
|
||||
resolved "https://registry.npm.taobao.org/qs/download/qs-6.5.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fqs%2Fdownload%2Fqs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||
|
||||
rc@^1.2.1:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.npm.taobao.org/rc/download/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||
dependencies:
|
||||
deep-extend "^0.6.0"
|
||||
ini "~1.3.0"
|
||||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
read-pkg-up@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npm.taobao.org/read-pkg-up/download/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
|
||||
dependencies:
|
||||
find-up "^1.0.0"
|
||||
read-pkg "^1.0.0"
|
||||
|
||||
read-pkg@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.npm.taobao.org/read-pkg/download/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
|
||||
dependencies:
|
||||
load-json-file "^1.0.0"
|
||||
normalize-package-data "^2.3.2"
|
||||
path-type "^1.0.0"
|
||||
|
||||
readable-stream@^2.2.2:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@~1.1.9:
|
||||
version "1.1.14"
|
||||
resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.1"
|
||||
isarray "0.0.1"
|
||||
string_decoder "~0.10.x"
|
||||
|
||||
redent@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npm.taobao.org/redent/download/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde"
|
||||
dependencies:
|
||||
indent-string "^2.1.0"
|
||||
strip-indent "^1.0.1"
|
||||
|
||||
repeating@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npm.taobao.org/repeating/download/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda"
|
||||
dependencies:
|
||||
is-finite "^1.0.0"
|
||||
|
||||
request@^2.45.0:
|
||||
version "2.88.0"
|
||||
resolved "https://registry.npm.taobao.org/request/download/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
|
||||
dependencies:
|
||||
aws-sign2 "~0.7.0"
|
||||
aws4 "^1.8.0"
|
||||
caseless "~0.12.0"
|
||||
combined-stream "~1.0.6"
|
||||
extend "~3.0.2"
|
||||
forever-agent "~0.6.1"
|
||||
form-data "~2.3.2"
|
||||
har-validator "~5.1.0"
|
||||
http-signature "~1.2.0"
|
||||
is-typedarray "~1.0.0"
|
||||
isstream "~0.1.2"
|
||||
json-stringify-safe "~5.0.1"
|
||||
mime-types "~2.1.19"
|
||||
oauth-sign "~0.9.0"
|
||||
performance-now "^2.1.0"
|
||||
qs "~6.5.2"
|
||||
safe-buffer "^5.1.2"
|
||||
tough-cookie "~2.4.3"
|
||||
tunnel-agent "^0.6.0"
|
||||
uuid "^3.3.2"
|
||||
|
||||
resolve@^1.10.0:
|
||||
version "1.11.1"
|
||||
resolved "https://registry.npm.taobao.org/resolve/download/resolve-1.11.1.tgz?cache=0&sync_timestamp=1561145359879&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve%2Fdownload%2Fresolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e"
|
||||
dependencies:
|
||||
path-parse "^1.0.6"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
|
||||
|
||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
||||
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
|
||||
"semver@2 || 3 || 4 || 5", semver@^5.4.1:
|
||||
version "5.7.0"
|
||||
resolved "https://registry.npm.taobao.org/semver/download/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
|
||||
|
||||
signal-exit@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npm.taobao.org/signal-exit/download/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||
|
||||
single-line-log@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.npm.taobao.org/single-line-log/download/single-line-log-1.1.2.tgz#c2f83f273a3e1a16edb0995661da0ed5ef033364"
|
||||
dependencies:
|
||||
string-width "^1.0.1"
|
||||
|
||||
source-map-support@^0.5.6:
|
||||
version "0.5.12"
|
||||
resolved "https://registry.npm.taobao.org/source-map-support/download/source-map-support-0.5.12.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fsource-map-support%2Fdownload%2Fsource-map-support-0.5.12.tgz#b4f3b10d51857a5af0138d3ce8003b201613d599"
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
source-map@^0.6.0:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.npm.taobao.org/source-map/download/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npm.taobao.org/spdx-correct/download/spdx-correct-3.1.0.tgz#fb83e504445268f154b074e218c87c003cd31df4"
|
||||
dependencies:
|
||||
spdx-expression-parse "^3.0.0"
|
||||
spdx-license-ids "^3.0.0"
|
||||
|
||||
spdx-exceptions@^2.1.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npm.taobao.org/spdx-exceptions/download/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977"
|
||||
|
||||
spdx-expression-parse@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npm.taobao.org/spdx-expression-parse/download/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
|
||||
dependencies:
|
||||
spdx-exceptions "^2.1.0"
|
||||
spdx-license-ids "^3.0.0"
|
||||
|
||||
spdx-license-ids@^3.0.0:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.npm.taobao.org/spdx-license-ids/download/spdx-license-ids-3.0.4.tgz#75ecd1a88de8c184ef015eafb51b5b48bfd11bb1"
|
||||
|
||||
speedometer@~0.1.2:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.npm.taobao.org/speedometer/download/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d"
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.16.1"
|
||||
resolved "https://registry.npm.taobao.org/sshpk/download/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
|
||||
dependencies:
|
||||
asn1 "~0.2.3"
|
||||
assert-plus "^1.0.0"
|
||||
bcrypt-pbkdf "^1.0.0"
|
||||
dashdash "^1.12.0"
|
||||
ecc-jsbn "~0.1.1"
|
||||
getpass "^0.1.1"
|
||||
jsbn "~0.1.0"
|
||||
safer-buffer "^2.0.2"
|
||||
tweetnacl "~0.14.0"
|
||||
|
||||
string-width@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npm.taobao.org/string-width/download/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
|
||||
dependencies:
|
||||
code-point-at "^1.0.0"
|
||||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
string_decoder@~0.10.x:
|
||||
version "0.10.31"
|
||||
resolved "https://registry.npm.taobao.org/string_decoder/download/string_decoder-0.10.31.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstring_decoder%2Fdownload%2Fstring_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npm.taobao.org/string_decoder/download/string_decoder-1.1.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstring_decoder%2Fdownload%2Fstring_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
strip-ansi@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npm.taobao.org/strip-ansi/download/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||
dependencies:
|
||||
ansi-regex "^2.0.0"
|
||||
|
||||
strip-bom@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npm.taobao.org/strip-bom/download/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
|
||||
dependencies:
|
||||
is-utf8 "^0.2.0"
|
||||
|
||||
strip-indent@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npm.taobao.org/strip-indent/download/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2"
|
||||
dependencies:
|
||||
get-stdin "^4.0.1"
|
||||
|
||||
strip-json-comments@~2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npm.taobao.org/strip-json-comments/download/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
|
||||
|
||||
sumchecker@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.npm.taobao.org/sumchecker/download/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e"
|
||||
dependencies:
|
||||
debug "^2.2.0"
|
||||
|
||||
throttleit@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.npm.taobao.org/throttleit/download/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf"
|
||||
|
||||
through2@~0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.npm.taobao.org/through2/download/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f"
|
||||
dependencies:
|
||||
readable-stream "~1.1.9"
|
||||
xtend "~2.1.1"
|
||||
|
||||
tough-cookie@~2.4.3:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.npm.taobao.org/tough-cookie/download/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
||||
dependencies:
|
||||
psl "^1.1.24"
|
||||
punycode "^1.4.1"
|
||||
|
||||
trim-newlines@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npm.taobao.org/trim-newlines/download/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
|
||||
|
||||
ts-node@^8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.npm.taobao.org/ts-node/download/ts-node-8.3.0.tgz#e4059618411371924a1fb5f3b125915f324efb57"
|
||||
dependencies:
|
||||
arg "^4.1.0"
|
||||
diff "^4.0.1"
|
||||
make-error "^1.1.1"
|
||||
source-map-support "^0.5.6"
|
||||
yn "^3.0.0"
|
||||
|
||||
tunnel-agent@^0.6.0:
|
||||
version "0.6.0"
|
||||
resolved "https://registry.npm.taobao.org/tunnel-agent/download/tunnel-agent-0.6.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftunnel-agent%2Fdownload%2Ftunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||
dependencies:
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||
version "0.14.5"
|
||||
resolved "https://registry.npm.taobao.org/tweetnacl/download/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||
|
||||
typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.npm.taobao.org/typedarray/download/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
|
||||
typescript@^3.5.3:
|
||||
version "3.5.3"
|
||||
resolved "https://registry.npm.taobao.org/typescript/download/typescript-3.5.3.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftypescript%2Fdownload%2Ftypescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
|
||||
|
||||
universalify@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.npm.taobao.org/universalify/download/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.npm.taobao.org/uri-js/download/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npm.taobao.org/util-deprecate/download/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
|
||||
uuid@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.npm.taobao.org/uuid/download/uuid-3.3.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fuuid%2Fdownload%2Fuuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||
|
||||
validate-npm-package-license@^3.0.1:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.npm.taobao.org/validate-npm-package-license/download/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
|
||||
dependencies:
|
||||
spdx-correct "^3.0.0"
|
||||
spdx-expression-parse "^3.0.0"
|
||||
|
||||
verror@1.10.0:
|
||||
version "1.10.0"
|
||||
resolved "https://registry.npm.taobao.org/verror/download/verror-1.10.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fverror%2Fdownload%2Fverror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
core-util-is "1.0.2"
|
||||
extsprintf "^1.2.0"
|
||||
|
||||
xtend@~2.1.1:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npm.taobao.org/xtend/download/xtend-2.1.2.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fxtend%2Fdownload%2Fxtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b"
|
||||
dependencies:
|
||||
object-keys "~0.4.0"
|
||||
|
||||
yauzl@2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.npm.taobao.org/yauzl/download/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005"
|
||||
dependencies:
|
||||
fd-slicer "~1.0.1"
|
||||
|
||||
yn@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npm.taobao.org/yn/download/yn-3.1.0.tgz#fcbe2db63610361afcc5eb9e0ac91e976d046114"
|
||||
@@ -8,6 +8,7 @@ module.exports = {
|
||||
'@vue/standard'
|
||||
],
|
||||
rules: {
|
||||
'comma-dangle': 'off',
|
||||
'no-console': 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
},
|
||||
|
||||
+136
-241
@@ -1,280 +1,175 @@
|
||||
<template>
|
||||
<div id="editor">
|
||||
</div>
|
||||
<div class="editor">
|
||||
<MonacoEditor
|
||||
ref="editor"
|
||||
class="editor"
|
||||
@change="val => $store.commit('app/setCurrentContent', val)"
|
||||
@ready="editorReady"
|
||||
@scroll-line="syncScrollView"
|
||||
@paste-img="pasteImg"
|
||||
@upload-file="uploadFile"
|
||||
@change-document="val => $store.commit('app/setDocumentInfo', val)"
|
||||
@xterm-run="val => $bus.emit('xterm-run', val)"
|
||||
@save="saveFile"></MonacoEditor>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import MonacoEditor from './MonacoEditor'
|
||||
import File from '@/lib/file'
|
||||
import dayjs from 'dayjs'
|
||||
import TurndownService from 'turndown'
|
||||
|
||||
const keys = {}
|
||||
|
||||
export default {
|
||||
name: 'editor',
|
||||
props: {
|
||||
value: String
|
||||
},
|
||||
components: { MonacoEditor },
|
||||
data () {
|
||||
return {
|
||||
editor: null
|
||||
timer: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (!(window).require) {
|
||||
let loaderScript = document.createElement('script')
|
||||
loaderScript.type = 'text/javascript'
|
||||
loaderScript.src = 'vs/loader.js'
|
||||
loaderScript.addEventListener('load', this.onGotAmdLoader)
|
||||
document.body.appendChild(loaderScript)
|
||||
} else {
|
||||
this.onGotAmdLoader()
|
||||
}
|
||||
window.addEventListener('resize', this.resize)
|
||||
window.addEventListener('keydown', this.recordKeys, true)
|
||||
window.addEventListener('keyup', this.recordKeys, true)
|
||||
this.$bus.on('editor-insert-value', this.insert)
|
||||
this.$bus.on('editor-replace-value', this.replaceValue)
|
||||
this.$bus.on('editor-toggle-wrap', this.toggleWrap)
|
||||
window.addEventListener('resize', this.$refs.editor.resize)
|
||||
this.$bus.on('resize', this.$refs.editor.resize)
|
||||
this.$bus.on('editor-insert-value', this.$refs.editor.insert)
|
||||
this.$bus.on('editor-replace-value', this.$refs.editor.replaceValue)
|
||||
this.$bus.on('editor-toggle-wrap', this.$refs.editor.toggleWrap)
|
||||
this.restartTimer()
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('paste', this.paste)
|
||||
window.removeEventListener('resize', this.resize)
|
||||
window.removeEventListener('keydown', this.recordKeys)
|
||||
window.removeEventListener('keyup', this.recordKeys)
|
||||
this.$bus.off('editor-insert-value', this.insert)
|
||||
this.$bus.off('editor-replace-value', this.replaceValue)
|
||||
this.$bus.off('editor-toggle-wrap', this.toggleWrap)
|
||||
window.removeEventListener('resize', this.$refs.editor.resize)
|
||||
this.$bus.off('resize', this.$refs.editor.resize)
|
||||
this.$bus.off('editor-insert-value', this.$refs.editor.insert)
|
||||
this.$bus.off('editor-replace-value', this.$refs.editor.replaceValue)
|
||||
this.$bus.off('editor-toggle-wrap', this.$refs.editor.toggleWrap)
|
||||
},
|
||||
methods: {
|
||||
recordKeys (e) {
|
||||
if (e.type === 'keydown') {
|
||||
keys[e.key] = true
|
||||
} else {
|
||||
keys[e.key] = false
|
||||
clearTimer () {
|
||||
if (this.timer) {
|
||||
window.clearTimeout(this.timer)
|
||||
}
|
||||
},
|
||||
resize () {
|
||||
if (this.editor) {
|
||||
this.editor.layout()
|
||||
restartTimer () {
|
||||
this.clearTimer()
|
||||
|
||||
if (!(this.currentFile && this.currentFile.repo && this.currentFile.path)) {
|
||||
return
|
||||
}
|
||||
},
|
||||
onGotAmdLoader () {
|
||||
window.require(['vs/editor/editor.main'], () => {
|
||||
this.initMonaco()
|
||||
})
|
||||
},
|
||||
initMonaco () {
|
||||
this.editor = window.monaco.editor.create(window.document.getElementById('editor'), {
|
||||
value: this.value,
|
||||
language: 'markdown',
|
||||
theme: 'vs-dark',
|
||||
fontSize: '20',
|
||||
wordWrap: false,
|
||||
// wordWrapColumn: 40,
|
||||
// Set this to false to not auto word wrap minified files
|
||||
wordWrapMinified: true,
|
||||
// try "same", "indent" or "none"
|
||||
wrappingIndent: 'same',
|
||||
smoothScrolling: true,
|
||||
cursorBlinking: 'smooth'
|
||||
})
|
||||
|
||||
this.editor.onDidChangeCursorSelection(e => {
|
||||
this.$bus.emit('change-document', this.getDucumentInfo())
|
||||
})
|
||||
|
||||
this.editor.onDidChangeModelContent(e => {
|
||||
let val = this.getValue()
|
||||
const eol = this.editor.getModel().getEOL()
|
||||
if (!val.endsWith(eol)) {
|
||||
val += eol
|
||||
this.setValue(val)
|
||||
this.timer = window.setTimeout(() => {
|
||||
if (!this.currentFile || this.currentFile.path.endsWith('.c.md')) { // 加密文件不自动保存
|
||||
return
|
||||
}
|
||||
this.$emit('input', val)
|
||||
})
|
||||
|
||||
this.editor.onDidScrollChange(e => {
|
||||
const line = this.editor.getVisibleRanges()[0].startLineNumber
|
||||
this.$emit('scroll-line', line)
|
||||
})
|
||||
|
||||
this.keyBind()
|
||||
window.addEventListener('paste', this.paste, true)
|
||||
|
||||
setTimeout(() => {
|
||||
this.$emit('ready')
|
||||
}, 500)
|
||||
this.saveFile()
|
||||
}, 2000)
|
||||
},
|
||||
keyBind () {
|
||||
const KM = window.monaco.KeyMod
|
||||
const KC = window.monaco.KeyCode
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Alt | KC.KEY_F, this.addAttachment)
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Alt | KC.KEY_D, () => {
|
||||
this.insert(dayjs().format('YYYY-MM-DD'))
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Alt | KC.KEY_T, () => {
|
||||
this.insert(dayjs().format('HH:mm:ss'))
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Alt | KC.KEY_R, () => {
|
||||
this.$bus.emit('run-in-terminal', this.editor.getModel().getValueInRange(this.editor.getSelection()))
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KC.Enter, () => {
|
||||
this.insert(this.editor.getModel().getEOL())
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.Shift | KC.Enter, () => {
|
||||
this.insert(this.editor.getModel().getOneIndent())
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Shift | KC.UpArrow, () => {
|
||||
this.editor.getAction('editor.action.moveLinesUpAction').run()
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Shift | KC.DownArrow, () => {
|
||||
this.editor.getAction('editor.action.moveLinesDownAction').run()
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Shift | KC.KEY_D, () => {
|
||||
this.editor.getAction('editor.action.copyLinesDownAction').run()
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KC.KEY_S, () => {
|
||||
this.$emit('save')
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KC.KEY_J, () => {
|
||||
this.editor.getAction('editor.action.joinLines').run()
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.chord(KM.CtrlCmd | KC.KEY_K, KM.CtrlCmd | KC.KEY_U), () => {
|
||||
this.editor.getAction('editor.action.transformToUppercase').run()
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.chord(KM.CtrlCmd | KC.KEY_K, KM.CtrlCmd | KC.KEY_L), () => {
|
||||
this.editor.getAction('editor.action.transformToLowercase').run()
|
||||
})
|
||||
|
||||
window.monaco.languages.setLanguageConfiguration('markdown', {
|
||||
onEnterRules: [
|
||||
{ beforeText: /^\s*> .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '> ' } },
|
||||
{ beforeText: /^\s*\+ \[ \] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '+ [ ] ' } },
|
||||
{ beforeText: /^\s*- \[ \] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '- [ ] ' } },
|
||||
{ beforeText: /^\s*\* \[ \] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '* [ ] ' } },
|
||||
{ beforeText: /^\s*\+ \[x\] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '+ [ ] ' } },
|
||||
{ beforeText: /^\s*- \[x\] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '- [ ] ' } },
|
||||
{ beforeText: /^\s*\* \[x\] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '* [ ] ' } },
|
||||
{ beforeText: /^\s*\+ .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '+ ' } },
|
||||
{ beforeText: /^\s*- .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '- ' } },
|
||||
{ beforeText: /^\s*\* .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '* ' } },
|
||||
{ beforeText: /^\s*1. .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '1. ' } }
|
||||
]
|
||||
})
|
||||
|
||||
// this.editor.addCommand(KC.Enter, () => {
|
||||
// const p = this.editor.getPosition()
|
||||
// this.editor.executeEdits('', [{
|
||||
// range: new window.monaco.Range(p.lineNumber, p.column, p.lineNumber, p.column),
|
||||
// text: '\nresult',
|
||||
// forceMoveMarkers: true
|
||||
// }])
|
||||
// console.log(this.editor.getModel().getLineContent(this.editor.getPosition().lineNumber))
|
||||
// })
|
||||
},
|
||||
paste (e) {
|
||||
if (this.editor.isFocused()) {
|
||||
const items = e.clipboardData.items
|
||||
if (keys['b'] || keys['B']) { // 粘贴 HTML 转为 markdown
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].type.match(/^text\/html$/i)) {
|
||||
items[i].getAsString(str => {
|
||||
const md = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced', bulletListMarker: '+' }).turndown(str)
|
||||
this.insert(md)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
} else {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].type.match(/^image\/(png|jpg|jpeg|gif)$/i)) {
|
||||
this.$emit('paste-img', items[i].getAsFile())
|
||||
}
|
||||
}
|
||||
}
|
||||
editorReady () {
|
||||
this.$bus.emit('editor-ready')
|
||||
if (this.currentFile) {
|
||||
this.changeFile(this.currentFile)
|
||||
}
|
||||
},
|
||||
addAttachment () {
|
||||
const input = window.document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.onchange = () => {
|
||||
for (let i = 0; i < input.files.length; i++) {
|
||||
this.$emit('upload-file', input.files[i])
|
||||
}
|
||||
}
|
||||
input.click()
|
||||
},
|
||||
insert (text) {
|
||||
const selection = this.editor.getSelection()
|
||||
this.editor.executeEdits('', [
|
||||
{
|
||||
range: new window.monaco.Range(selection.endLineNumber, selection.endColumn, selection.endLineNumber, selection.endColumn),
|
||||
text,
|
||||
forceMoveMarkers: true
|
||||
}
|
||||
])
|
||||
},
|
||||
replaceLine (line, text) {
|
||||
const length = this.editor.getModel().getLineLength(line)
|
||||
|
||||
this.editor.executeEdits('', [
|
||||
{ range: new window.monaco.Range(line, 1, line, length + 1), text }
|
||||
])
|
||||
},
|
||||
revealLine (line) {
|
||||
this.editor.revealLineInCenter(line)
|
||||
this.$refs.editor.revealLine(line)
|
||||
},
|
||||
switchTodo (line, checked) {
|
||||
if (checked) {
|
||||
const value = this.editor.getModel().getLineContent(line).replace('[ ]', `[x] ~~${dayjs().format('YYYY-MM-DD HH:mm')}~~`)
|
||||
this.replaceLine(line, value)
|
||||
} else {
|
||||
const value = this.editor.getModel().getLineContent(line).replace(/(\[x\] ~~[\d-: ]+~~|\[x\])/, '[ ]')
|
||||
this.replaceLine(line, value)
|
||||
}
|
||||
this.$refs.editor.switchTodo(line, checked)
|
||||
},
|
||||
getValue () {
|
||||
return this.editor.getModel().getValue(window.monaco.editor.DefaultEndOfLine.LF)
|
||||
syncScrollView (e) {
|
||||
this.$emit('scroll-line', e)
|
||||
},
|
||||
setValue (val) {
|
||||
this.editor.getModel().setValue(val)
|
||||
},
|
||||
replaceValue (oldValue, newValue) {
|
||||
this.setValue(this.getValue().replace(oldValue, newValue))
|
||||
},
|
||||
getDucumentInfo () {
|
||||
const selection = this.editor.getSelection()
|
||||
saveFile (f = null) {
|
||||
const file = f || this.currentFile
|
||||
|
||||
return {
|
||||
line: selection.positionLineNumber,
|
||||
column: selection.positionColumn,
|
||||
lineCount: this.editor.getModel().getLineCount(),
|
||||
textLength: this.getValue().length,
|
||||
selectedLength: this.editor.getModel().getValueInRange(selection).length
|
||||
if (!(file && file.repo && file.path)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.previousContent === this.currentContent) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.currentContent) {
|
||||
return
|
||||
}
|
||||
|
||||
if (file.repo === '__readme__') {
|
||||
return
|
||||
}
|
||||
|
||||
const content = this.currentContent
|
||||
File.write(file.repo, file.path, content, this.previousHash, result => {
|
||||
this.$store.commit('app/setPreviousHash', result.data)
|
||||
this.$store.commit('app/setPreviousContent', content)
|
||||
this.$store.commit('app/setSavedAt', new Date())
|
||||
}, e => {
|
||||
alert(e.message)
|
||||
})
|
||||
},
|
||||
setPosition (position) {
|
||||
this.editor.setScrollTop(0)
|
||||
pasteImg (file) {
|
||||
File.upload(this.currentFile.repo, this.currentFile.path, file, ({ relativePath }) => {
|
||||
this.$bus.emit('file-uploaded', relativePath)
|
||||
this.$refs.editor.insert(`})\n`)
|
||||
})
|
||||
},
|
||||
toggleWrap () {
|
||||
const isWrapping = this.editor.getConfiguration().wrappingInfo.isViewportWrapping
|
||||
this.editor.updateOptions({ wordWrap: !isWrapping })
|
||||
uploadFile (file) {
|
||||
File.upload(this.currentFile.repo, this.currentFile.path, file, ({ relativePath }) => {
|
||||
this.$bus.emit('file-uploaded', relativePath)
|
||||
this.$refs.editor.insert(`附件 [${dayjs().format('YYYY-MM-DD HH:mm')}]:[${file.name} (${(file.size / 1024).toFixed(2)}KiB)](${encodeURI(relativePath).replace('(', '%28').replace(')', '%29')}){class=open target=_blank}\n`)
|
||||
}, `${dayjs().format('YYYYMMDDHHmmss')}.${file.name}`)
|
||||
},
|
||||
changeFile (current, previous) {
|
||||
this.clearTimer()
|
||||
|
||||
if (previous && previous.repo && previous.path) {
|
||||
this.saveFile(previous)
|
||||
}
|
||||
|
||||
if (current) {
|
||||
if (current.content) { // 系统文件
|
||||
this.$store.commit('app/setPreviousContent', current.content)
|
||||
this.$store.commit('app/setSavedAt', null)
|
||||
this.$refs.editor.setValue(current.content)
|
||||
} else {
|
||||
File.read(current, (data, hash) => {
|
||||
this.$store.commit('app/setPreviousContent', data)
|
||||
this.$store.commit('app/setPreviousHash', hash)
|
||||
this.$store.commit('app/setSavedAt', null)
|
||||
this.$refs.editor.setValue(data)
|
||||
}, e => {
|
||||
this.$store.commit('app/setCurrentFile', null)
|
||||
alert(e.message)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
this.$store.commit('app/setPreviousContent', '\n')
|
||||
this.$refs.editor.setValue('\n')
|
||||
this.$store.commit('app/setCurrentFile', null)
|
||||
}
|
||||
|
||||
// 切换文件时候定位到第一行
|
||||
this.$refs.editor.setPosition({ column: 1, lineNumber: 1 })
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['currentFile', 'currentContent', 'previousContent', 'previousHash'])
|
||||
},
|
||||
watch: {
|
||||
currentFile (current, previous) {
|
||||
this.changeFile(current, previous)
|
||||
},
|
||||
currentContent () {
|
||||
this.restartTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.editor {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,317 +1,77 @@
|
||||
<template>
|
||||
<div @keydown.tab.stop="switchTab()" @keydown.enter="chooseItem()" @keydown.up="selectItem(-1)" @keydown.down="selectItem(1)" class="filter" @click.stop>
|
||||
<div class="tab">
|
||||
<div @click="switchTab('file')" :class="{selected: currentTab === 'file'}">快速跳转</div>
|
||||
<div @click="switchTab('search')" :class="{selected: currentTab === 'search'}">搜索内容</div>
|
||||
<!-- <div>标签</div> -->
|
||||
<transition name="fade">
|
||||
<div v-if="show" class="filter-wrapper" @click="show = false">
|
||||
<QuickOpen @choose-file="show" @close="show = false"></QuickOpen>
|
||||
</div>
|
||||
<input ref="input" v-model="searchText" type="text" class="input" @keydown.tab.prevent @keydown.up.prevent @keydown.down.prevent autofocus>
|
||||
<ul ref="result" class="result">
|
||||
<li v-if="list === null">加载中……</li>
|
||||
<template v-else>
|
||||
<li
|
||||
v-for="item in list"
|
||||
:key="item.path"
|
||||
:class="{selected: selected === item}"
|
||||
@click="chooseItem(item)">
|
||||
<span ref="fileName">
|
||||
{{item.name}}
|
||||
</span>
|
||||
<span ref="filePath" class="path">
|
||||
{{item.path.substr(0, item.path.lastIndexOf('/'))}}
|
||||
</span>
|
||||
</li>
|
||||
<li v-if="list.length < 1">无结果</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import file from '../file'
|
||||
import fuzzyMatch from '../fuzzyMatch'
|
||||
import QuickOpen from './QuickOpen'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'x-filter',
|
||||
components: {},
|
||||
props: {
|
||||
files: Array,
|
||||
repo: String
|
||||
},
|
||||
components: { QuickOpen },
|
||||
data () {
|
||||
return {
|
||||
selected: null,
|
||||
searchText: '',
|
||||
currentTab: 'file',
|
||||
list: [],
|
||||
lastFetchTime: 0
|
||||
show: false,
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.searchWithDebounce = _.debounce((text, call) => {
|
||||
if (this.repo && text.trim()) {
|
||||
const fetchTime = new Date().getTime()
|
||||
this.lastFetchTime = fetchTime
|
||||
file.search(this.repo, text.trim(), data => {
|
||||
// 总是保证最后的搜索结果出现在列表
|
||||
if (fetchTime >= this.lastFetchTime) {
|
||||
call(data)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.list = []
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
mounted () {
|
||||
this.$refs.input.focus()
|
||||
this.updateDataSource()
|
||||
window.addEventListener('keydown', this.keydownHandler, true)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keydownHandler)
|
||||
},
|
||||
methods: {
|
||||
highlightText (search) {
|
||||
if (this.$refs.fileName && this.$refs.filePath) {
|
||||
search = search.toLowerCase()
|
||||
|
||||
const openF = '(#$*B'
|
||||
const closeF = '#$*B)'
|
||||
|
||||
const escape = function (s) {
|
||||
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
||||
keydownHandler (e) {
|
||||
if (e.key === 'i' && e.ctrlKey && e.altKey) {
|
||||
this.show = f => {
|
||||
if (this.currentFile) {
|
||||
const relativePath = f.path.replace(this.currentFile.path.substr(0, this.currentFile.path.lastIndexOf('/')), '.')
|
||||
this.$bus.emit('editor-insert-value', `[${f.name.replace(/\.[^.]$/, '')}](${encodeURI(relativePath)})`)
|
||||
}
|
||||
this.show = false
|
||||
}
|
||||
|
||||
const openR = new RegExp(escape(openF), 'g')
|
||||
const closeR = new RegExp(escape(closeF), 'g')
|
||||
|
||||
this.$refs.fileName.concat(this.$refs.filePath).forEach(function (it) {
|
||||
let text = ''
|
||||
|
||||
it.innerText.toLowerCase().split('').forEach(char => {
|
||||
if (search.indexOf(char) > -1) {
|
||||
text += `${openF}${char}${closeF}`
|
||||
} else {
|
||||
text += char
|
||||
}
|
||||
})
|
||||
|
||||
it.innerText = text
|
||||
|
||||
it.innerHTML = it.innerHTML.replace(openR, '<b>').replace(closeR, '</b>')
|
||||
})
|
||||
}
|
||||
},
|
||||
filterFiles (files, search) {
|
||||
const tmp = []
|
||||
|
||||
files.forEach(x => {
|
||||
const result = fuzzyMatch(search, x.path)
|
||||
|
||||
if (result.matched) {
|
||||
tmp.push({ ...x })
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
} else if (e.key === 'p' && e.ctrlKey) {
|
||||
this.show = f => {
|
||||
this.$store.commit('app/setCurrentFile', f)
|
||||
this.show = false
|
||||
}
|
||||
})
|
||||
|
||||
return tmp.sort((a, b) => b.score - a.score)
|
||||
},
|
||||
updateDataSource () {
|
||||
if (this.currentTab === 'file') {
|
||||
this.list = this.filterFiles(this.files, this.searchText.trim())
|
||||
this.sortList()
|
||||
} else {
|
||||
this.list = null
|
||||
this.searchWithDebounce(this.searchText.trim(), data => {
|
||||
this.list = data
|
||||
this.sortList()
|
||||
})
|
||||
}
|
||||
|
||||
this.$nextTick(this.highlightText(this.searchText.trim()))
|
||||
},
|
||||
updateSelected (item = null) {
|
||||
if (this.list === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (item) {
|
||||
this.selected = item
|
||||
} else {
|
||||
this.selected = this.list.length > 0 ? this.list[0] : null
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
const li = this.$refs.result.querySelector('li.selected')
|
||||
if (li) {
|
||||
li.scrollIntoViewIfNeeded()
|
||||
}
|
||||
})
|
||||
},
|
||||
selectItem (inc) {
|
||||
if (this.list.length < 1) {
|
||||
this.updateSelected()
|
||||
return
|
||||
}
|
||||
|
||||
const currentIndex = this.list.findIndex(x => this.selected === x)
|
||||
|
||||
let index = currentIndex + inc
|
||||
if (index > this.list.length - 1) {
|
||||
index = 0
|
||||
} else if (index < 0) {
|
||||
index = this.list.length - 1
|
||||
}
|
||||
|
||||
this.updateSelected(this.list[index])
|
||||
},
|
||||
chooseItem (item = null) {
|
||||
const file = item || this.selected
|
||||
if (file) {
|
||||
this.$emit('choose-file', file)
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
} else if (e.key === 'Escape' && this.show) {
|
||||
this.show = false
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
},
|
||||
switchTab (tab = null) {
|
||||
if (tab) {
|
||||
this.currentTab = tab
|
||||
return
|
||||
}
|
||||
|
||||
if (this.currentTab === 'file') {
|
||||
this.currentTab = 'search'
|
||||
} else if (this.currentTab === 'search') {
|
||||
this.currentTab = 'file'
|
||||
}
|
||||
},
|
||||
sortList () {
|
||||
if (this.list === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const json = window.localStorage[`${this.repo}_open_time`] || '{}'
|
||||
|
||||
let map = {}
|
||||
try {
|
||||
map = JSON.parse(json)
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
this.list.sort((a, b) => {
|
||||
const at = map[a.path] || 0
|
||||
const bt = map[b.path] || 0
|
||||
|
||||
return bt - at
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list () {
|
||||
this.updateSelected()
|
||||
},
|
||||
searchText () {
|
||||
this.updateDataSource()
|
||||
},
|
||||
currentTab () {
|
||||
this.list = null
|
||||
this.$refs.input.focus()
|
||||
this.updateDataSource()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['currentFile'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter {
|
||||
width: 600px;
|
||||
background: #403e3e;
|
||||
margin: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.input {
|
||||
display: block;
|
||||
.filter-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
font-size: 18px;
|
||||
line-height: 1.4em;
|
||||
padding: 6px;
|
||||
box-sizing: border-box;
|
||||
background: #333030;
|
||||
color: #ddd;
|
||||
transition: all .1s ease-in-out;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, .2);
|
||||
z-index: 99999;
|
||||
padding-top: 4em;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
background: #242222;
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
.result {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.result li {
|
||||
color: #999;
|
||||
line-height: 1.5em;
|
||||
font-size: 18px;
|
||||
padding: 2px;
|
||||
transition: all .1s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.result li.selected,
|
||||
.result li:hover {
|
||||
padding: 2px 6px;
|
||||
background: #333030;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.result li span {
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
.result li span.path {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
padding-left: .3em;
|
||||
}
|
||||
|
||||
.result li span.path /deep/ b {
|
||||
color: #ababab;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result li /deep/ b {
|
||||
color: #c6d2ca;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
.tab > div {
|
||||
flex: auto;
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
font-size: 12px;
|
||||
padding: 4px 0;
|
||||
background: #403e3e;
|
||||
cursor: pointer;
|
||||
transition: all .2s ease-in-out;
|
||||
}
|
||||
|
||||
.tab > div:hover {
|
||||
background: #444141;
|
||||
}
|
||||
|
||||
.tab > div.selected {
|
||||
background: #333030;
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div class="layout">
|
||||
<div class="header">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="left" ref="aside">
|
||||
<slot name="left"></slot>
|
||||
<div class="sash-right" @mousedown="e => initResize('right', 'aside', 100, 700, e)"></div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="content">
|
||||
<div class="editor" ref="editor">
|
||||
<slot name="editor"></slot>
|
||||
<!-- <div class="sash-right" @mousedown="e => initResize('right', 'editor', 100, 99999999999, e)"></div> -->
|
||||
</div>
|
||||
<div class="preview" v-show="showView">
|
||||
<slot name="preview"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="terminal" ref="terminal" v-show="showXterm">
|
||||
<slot name="terminal"></slot>
|
||||
<div class="sash-top" @mousedown="e => initResize('top', 'terminal', 70, 500, e)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
let resizeOrigin = null
|
||||
|
||||
export default {
|
||||
name: 'layout',
|
||||
mounted () {
|
||||
window.document.addEventListener('mousemove', this.resizeFrame)
|
||||
this.$bus.on('toggle-view', this.toggleView)
|
||||
this.$bus.on('toggle-xterm', this.toggleXterm)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.document.removeEventListener('mousemove', this.resizeFrame)
|
||||
this.$bus.off('toggle-view', this.toggleView)
|
||||
this.$bus.off('toggle-xterm', this.toggleXterm)
|
||||
},
|
||||
methods: {
|
||||
toggleView () {
|
||||
this.$store.commit('app/setShowView', !this.showView)
|
||||
this.$nextTick(() => this.$bus.emit('resize'), 500)
|
||||
},
|
||||
toggleXterm (val) {
|
||||
const showXterm = typeof val === 'boolean' ? val : !this.showXterm
|
||||
|
||||
this.$store.commit('app/setShowXterm', showXterm)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$nextTick(() => this.$bus.emit('resize'), 500)
|
||||
|
||||
if (showXterm) {
|
||||
this.$bus.emit('xterm-init')
|
||||
}
|
||||
})
|
||||
},
|
||||
initResize (type, ref, min, max, e) {
|
||||
if (!resizeOrigin && type) {
|
||||
resizeOrigin = {
|
||||
min,
|
||||
max,
|
||||
type,
|
||||
ref,
|
||||
mouseX: e.clientX,
|
||||
mouseY: e.clientY,
|
||||
targetWidth: this.$refs[ref].clientWidth,
|
||||
targetHeight: this.$refs[ref].clientHeight,
|
||||
targetLeft: this.$refs[ref].clientLeft,
|
||||
targetTop: this.$refs[ref].clientTop,
|
||||
}
|
||||
}
|
||||
},
|
||||
resizeFrame (e) {
|
||||
if (e.buttons !== 1) {
|
||||
resizeOrigin = null
|
||||
return
|
||||
}
|
||||
|
||||
if (!resizeOrigin) {
|
||||
return
|
||||
}
|
||||
|
||||
if (resizeOrigin.type === 'right') {
|
||||
const offsetX = e.clientX - resizeOrigin.mouseX
|
||||
const width = (resizeOrigin.targetWidth + offsetX)
|
||||
this.$refs[resizeOrigin.ref].style.width = Math.min(resizeOrigin.max, Math.max(resizeOrigin.min, width)) + 'px'
|
||||
} else if (resizeOrigin.type === 'top') {
|
||||
const offsetY = -(e.clientY - resizeOrigin.mouseY)
|
||||
const height = (resizeOrigin.targetHeight + offsetY)
|
||||
this.$refs[resizeOrigin.ref].style.height = Math.min(resizeOrigin.max, Math.max(resizeOrigin.min, height)) + 'px'
|
||||
}
|
||||
this.$bus.emit('resize')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['showView', 'showXterm'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.main {
|
||||
display: flex;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.left {
|
||||
position: relative;
|
||||
width: 17vw;
|
||||
flex: none;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sash-right {
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 4px;
|
||||
cursor: w-resize;
|
||||
}
|
||||
|
||||
.sash-top {
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 4px;
|
||||
cursor: s-resize;
|
||||
}
|
||||
|
||||
.right {
|
||||
flex: 1;
|
||||
width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editor {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
height: 200px;
|
||||
flex: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.preview {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100vw;
|
||||
height: 20px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.terminal,
|
||||
.left,
|
||||
.editor,
|
||||
.header,
|
||||
.footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.preview {
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.layout {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,278 @@
|
||||
<template>
|
||||
<div id="editor"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from 'dayjs'
|
||||
import TurndownService from 'turndown'
|
||||
|
||||
const keys = {}
|
||||
|
||||
export default {
|
||||
name: 'monaco-editor',
|
||||
props: {
|
||||
value: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editor: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (!(window).require) {
|
||||
let loaderScript = document.createElement('script')
|
||||
loaderScript.type = 'text/javascript'
|
||||
loaderScript.src = 'vs/loader.js'
|
||||
loaderScript.addEventListener('load', this.onGotAmdLoader)
|
||||
document.body.appendChild(loaderScript)
|
||||
} else {
|
||||
this.onGotAmdLoader()
|
||||
}
|
||||
window.addEventListener('keydown', this.recordKeys, true)
|
||||
window.addEventListener('keyup', this.recordKeys, true)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('paste', this.paste)
|
||||
window.removeEventListener('keydown', this.recordKeys)
|
||||
window.removeEventListener('keyup', this.recordKeys)
|
||||
},
|
||||
methods: {
|
||||
recordKeys (e) {
|
||||
if (e.type === 'keydown') {
|
||||
keys[e.key] = true
|
||||
} else {
|
||||
keys[e.key] = false
|
||||
}
|
||||
},
|
||||
resize () {
|
||||
if (this.editor) {
|
||||
this.editor.layout()
|
||||
}
|
||||
},
|
||||
onGotAmdLoader () {
|
||||
window.require(['vs/editor/editor.main'], () => {
|
||||
this.initMonaco()
|
||||
})
|
||||
},
|
||||
initMonaco () {
|
||||
this.editor = window.monaco.editor.create(window.document.getElementById('editor'), {
|
||||
value: this.value,
|
||||
language: 'markdown',
|
||||
theme: 'vs-dark',
|
||||
fontSize: '20',
|
||||
wordWrap: false,
|
||||
// wordWrapColumn: 40,
|
||||
// Set this to false to not auto word wrap minified files
|
||||
wordWrapMinified: true,
|
||||
// try "same", "indent" or "none"
|
||||
wrappingIndent: 'same',
|
||||
smoothScrolling: true,
|
||||
cursorBlinking: 'smooth'
|
||||
})
|
||||
|
||||
this.editor.onDidChangeCursorSelection(e => {
|
||||
this.$emit('change-document', this.getDucumentInfo())
|
||||
})
|
||||
|
||||
this.editor.onDidChangeModelContent(e => {
|
||||
let val = this.getValue()
|
||||
const eol = this.editor.getModel().getEOL()
|
||||
if (!val.endsWith(eol)) {
|
||||
val += eol
|
||||
this.setValue(val)
|
||||
}
|
||||
this.$emit('change', val)
|
||||
})
|
||||
|
||||
this.editor.onDidScrollChange(e => {
|
||||
const line = this.editor.getVisibleRanges()[0].startLineNumber
|
||||
this.$emit('scroll-line', line)
|
||||
})
|
||||
|
||||
this.keyBind()
|
||||
window.addEventListener('paste', this.paste, true)
|
||||
|
||||
setTimeout(() => {
|
||||
this.$emit('ready')
|
||||
}, 500)
|
||||
},
|
||||
keyBind () {
|
||||
const KM = window.monaco.KeyMod
|
||||
const KC = window.monaco.KeyCode
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Alt | KC.KEY_F, this.addAttachment)
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Alt | KC.KEY_D, () => {
|
||||
this.insert(dayjs().format('YYYY-MM-DD'))
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Alt | KC.KEY_T, () => {
|
||||
this.insert(dayjs().format('HH:mm:ss'))
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Alt | KC.KEY_R, () => {
|
||||
this.$emit('xterm-run', this.editor.getModel().getValueInRange(this.editor.getSelection()))
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KC.Enter, () => {
|
||||
this.insert(this.editor.getModel().getEOL())
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.Shift | KC.Enter, () => {
|
||||
this.insert(this.editor.getModel().getOneIndent())
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Shift | KC.UpArrow, () => {
|
||||
this.editor.getAction('editor.action.moveLinesUpAction').run()
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Shift | KC.DownArrow, () => {
|
||||
this.editor.getAction('editor.action.moveLinesDownAction').run()
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KM.Shift | KC.KEY_D, () => {
|
||||
this.editor.getAction('editor.action.copyLinesDownAction').run()
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KC.KEY_S, () => {
|
||||
this.$emit('save')
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.CtrlCmd | KC.KEY_J, () => {
|
||||
this.editor.getAction('editor.action.joinLines').run()
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.chord(KM.CtrlCmd | KC.KEY_K, KM.CtrlCmd | KC.KEY_U), () => {
|
||||
this.editor.getAction('editor.action.transformToUppercase').run()
|
||||
})
|
||||
|
||||
this.editor.addCommand(KM.chord(KM.CtrlCmd | KC.KEY_K, KM.CtrlCmd | KC.KEY_L), () => {
|
||||
this.editor.getAction('editor.action.transformToLowercase').run()
|
||||
})
|
||||
|
||||
window.monaco.languages.setLanguageConfiguration('markdown', {
|
||||
onEnterRules: [
|
||||
{ beforeText: /^\s*> .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '> ' } },
|
||||
{ beforeText: /^\s*\+ \[ \] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '+ [ ] ' } },
|
||||
{ beforeText: /^\s*- \[ \] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '- [ ] ' } },
|
||||
{ beforeText: /^\s*\* \[ \] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '* [ ] ' } },
|
||||
{ beforeText: /^\s*\+ \[x\] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '+ [ ] ' } },
|
||||
{ beforeText: /^\s*- \[x\] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '- [ ] ' } },
|
||||
{ beforeText: /^\s*\* \[x\] .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '* [ ] ' } },
|
||||
{ beforeText: /^\s*\+ .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '+ ' } },
|
||||
{ beforeText: /^\s*- .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '- ' } },
|
||||
{ beforeText: /^\s*\* .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '* ' } },
|
||||
{ beforeText: /^\s*1. .*$/, action: { indentAction: window.monaco.languages.IndentAction.None, appendText: '1. ' } }
|
||||
]
|
||||
})
|
||||
|
||||
// this.editor.addCommand(KC.Enter, () => {
|
||||
// const p = this.editor.getPosition()
|
||||
// this.editor.executeEdits('', [{
|
||||
// range: new window.monaco.Range(p.lineNumber, p.column, p.lineNumber, p.column),
|
||||
// text: '\nresult',
|
||||
// forceMoveMarkers: true
|
||||
// }])
|
||||
// console.log(this.editor.getModel().getLineContent(this.editor.getPosition().lineNumber))
|
||||
// })
|
||||
},
|
||||
paste (e) {
|
||||
if (this.editor.isFocused()) {
|
||||
const items = e.clipboardData.items
|
||||
if (keys['b'] || keys['B']) { // 粘贴 HTML 转为 markdown
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].type.match(/^text\/html$/i)) {
|
||||
items[i].getAsString(str => {
|
||||
const md = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced', bulletListMarker: '+' }).turndown(str)
|
||||
this.insert(md)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
} else {
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
if (items[i].type.match(/^image\/(png|jpg|jpeg|gif)$/i)) {
|
||||
this.$emit('paste-img', items[i].getAsFile())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
addAttachment () {
|
||||
const input = window.document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.onchange = () => {
|
||||
for (let i = 0; i < input.files.length; i++) {
|
||||
this.$emit('upload-file', input.files[i])
|
||||
}
|
||||
}
|
||||
input.click()
|
||||
},
|
||||
insert (text) {
|
||||
const selection = this.editor.getSelection()
|
||||
this.editor.executeEdits('', [
|
||||
{
|
||||
range: new window.monaco.Range(selection.endLineNumber, selection.endColumn, selection.endLineNumber, selection.endColumn),
|
||||
text,
|
||||
forceMoveMarkers: true
|
||||
}
|
||||
])
|
||||
},
|
||||
replaceLine (line, text) {
|
||||
const length = this.editor.getModel().getLineLength(line)
|
||||
|
||||
this.editor.executeEdits('', [
|
||||
{ range: new window.monaco.Range(line, 1, line, length + 1), text }
|
||||
])
|
||||
},
|
||||
revealLine (line) {
|
||||
this.editor.revealLineInCenter(line)
|
||||
},
|
||||
switchTodo (line, checked) {
|
||||
if (checked) {
|
||||
const value = this.editor.getModel().getLineContent(line).replace('[ ]', `[x] ~~${dayjs().format('YYYY-MM-DD HH:mm')}~~`)
|
||||
this.replaceLine(line, value)
|
||||
} else {
|
||||
const value = this.editor.getModel().getLineContent(line).replace(/(\[x\] ~~[\d-: ]+~~|\[x\])/, '[ ]')
|
||||
this.replaceLine(line, value)
|
||||
}
|
||||
},
|
||||
getValue () {
|
||||
return this.editor.getModel().getValue(window.monaco.editor.DefaultEndOfLine.LF)
|
||||
},
|
||||
setValue (val) {
|
||||
this.editor.getModel().setValue(val)
|
||||
},
|
||||
replaceValue (oldValue, newValue) {
|
||||
this.setValue(this.getValue().replace(oldValue, newValue))
|
||||
},
|
||||
getDucumentInfo () {
|
||||
const selection = this.editor.getSelection()
|
||||
|
||||
return {
|
||||
line: selection.positionLineNumber,
|
||||
column: selection.positionColumn,
|
||||
lineCount: this.editor.getModel().getLineCount(),
|
||||
textLength: this.getValue().length,
|
||||
selectedLength: this.editor.getModel().getValueInRange(selection).length
|
||||
}
|
||||
},
|
||||
setPosition (position) {
|
||||
this.editor.setScrollTop(0)
|
||||
},
|
||||
toggleWrap () {
|
||||
const isWrapping = this.editor.getConfiguration().wrappingInfo.isViewportWrapping
|
||||
this.editor.updateOptions({ wordWrap: !isWrapping })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#editor {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div ref="view-wrapper" class="view" @scroll="handleScroll">
|
||||
<div class="action-bar">
|
||||
<div class="action-bar" :style="{width: (width - 50) + 'px'}">
|
||||
<div :style="{background: todoCount ? '#4e4e4e' : 'transparent'}">
|
||||
<div v-if="todoCount" class="todo-progress">
|
||||
<div :style="{
|
||||
@@ -22,21 +22,20 @@
|
||||
</div>
|
||||
<div ref="outline" class="outline">
|
||||
<div style="padding: .5em;"><b>目录</b></div>
|
||||
<div class="catalog">
|
||||
<div class="catalog" :style="{maxHeight: (height - 120) + 'px'}">
|
||||
<div v-for="(head, index) in heads" :key="index" :style="{paddingLeft: `${head.level + 1}em`}" @click="syncScroll(head.sourceLine)">
|
||||
{{ head.text }}
|
||||
<span style="color: #666;font-size: 12px;padding-left: .5em">{{head.tag}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="{'scroll-to-top': true, 'show-xterm': showXterm, 'hide': scrollTop < 30}" @click="scrollToTop">TOP</div>
|
||||
<div :class="{'scroll-to-top': true, 'hide': scrollTop < 30}" :style="{top: (height - 40) + 'px'}" @click="scrollToTop">TOP</div>
|
||||
<article ref="view" class="markdown-body" @click="handleClick"></article>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'github-markdown-css/github-markdown.css'
|
||||
import 'highlight.js/styles/atom-one-dark.css'
|
||||
import { mapState } from 'vuex'
|
||||
import _ from 'lodash'
|
||||
import mime from 'mime-types'
|
||||
import Markdown from 'markdown-it'
|
||||
@@ -53,51 +52,49 @@ import RunPlugin from '../plugins/RunPlugin'
|
||||
import SourceLinePlugin from '../plugins/SourceLinePlugin'
|
||||
import LinkTargetPlugin from '../plugins/LinkTargetPlugin'
|
||||
import PlantumlPlugin from '../plugins/PlantumlPlugin'
|
||||
import file from '../file'
|
||||
import file from '@/lib/file'
|
||||
|
||||
import 'github-markdown-css/github-markdown.css'
|
||||
import 'highlight.js/styles/atom-one-dark.css'
|
||||
import 'katex/dist/katex.min.css'
|
||||
HighlightLineNumber.addStyles()
|
||||
|
||||
const markdown = Markdown({
|
||||
linkify: true,
|
||||
breaks: true,
|
||||
html: true,
|
||||
highlight: (str, lang) => {
|
||||
if (lang && Highlight.getLanguage(lang)) {
|
||||
try {
|
||||
return Highlight.highlight(lang, str).value
|
||||
} catch (__) {}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
})
|
||||
.use(TaskLists, { enabled: true })
|
||||
.use(PlantumlPlugin)
|
||||
.use(RunPlugin)
|
||||
.use(katex)
|
||||
.use(SourceLinePlugin)
|
||||
.use(MarkdownItAttrs)
|
||||
.use(LinkTargetPlugin)
|
||||
.use(MultimdTable, { enableMultilineRows: true })
|
||||
.use(MarkdownItToc)
|
||||
.use(MarkdownItECharts)
|
||||
|
||||
export default {
|
||||
name: 'xview',
|
||||
props: {
|
||||
value: String,
|
||||
fileRepo: String,
|
||||
fileName: String,
|
||||
filePath: String,
|
||||
showXterm: Boolean
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
width: 1024,
|
||||
height: 1024,
|
||||
heads: [],
|
||||
convert: {},
|
||||
todoCount: 0,
|
||||
todoDoneCount: 0,
|
||||
scrollTop: 0,
|
||||
markdown: Markdown({
|
||||
linkify: true,
|
||||
breaks: true,
|
||||
html: true,
|
||||
highlight: (str, lang) => {
|
||||
if (lang && Highlight.getLanguage(lang)) {
|
||||
try {
|
||||
return Highlight.highlight(lang, str).value
|
||||
} catch (__) {}
|
||||
}
|
||||
|
||||
return ''
|
||||
}
|
||||
})
|
||||
.use(TaskLists, { enabled: true })
|
||||
.use(PlantumlPlugin)
|
||||
.use(RunPlugin)
|
||||
.use(katex)
|
||||
.use(SourceLinePlugin)
|
||||
.use(MarkdownItAttrs)
|
||||
.use(LinkTargetPlugin)
|
||||
.use(MultimdTable, { enableMultilineRows: true })
|
||||
.use(MarkdownItToc)
|
||||
.use(MarkdownItECharts)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
@@ -106,7 +103,7 @@ export default {
|
||||
}, 3000)
|
||||
|
||||
this.render = _.debounce(() => {
|
||||
this.$refs.view.innerHTML = this.markdown.render(this.replaceRelativeLink(this.value))
|
||||
this.$refs.view.innerHTML = markdown.render(this.replaceRelativeLink(this.currentContent))
|
||||
MarkdownItECharts.update()
|
||||
this.updateOutline()
|
||||
this.updateTodoCount()
|
||||
@@ -133,12 +130,18 @@ export default {
|
||||
}, 100)
|
||||
|
||||
window.addEventListener('keydown', this.keydownHandler, true)
|
||||
this.$bus.on('resize', this.resizeHandler)
|
||||
this.resizeHandler()
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keydownHandler)
|
||||
this.$bus.off('change-document', this.handleChangeDocument)
|
||||
this.$bus.off('resize', this.resizeHandler)
|
||||
},
|
||||
methods: {
|
||||
resizeHandler () {
|
||||
this.width = this.$refs['view-wrapper'].clientWidth
|
||||
this.height = this.$refs['view-wrapper'].clientHeight
|
||||
},
|
||||
keydownHandler (e) {
|
||||
// 转换所有外链图片到本地
|
||||
if (e.key === 'l' && e.ctrlKey && e.altKey) {
|
||||
@@ -295,8 +298,20 @@ export default {
|
||||
window.print()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['currentContent', 'currentFile']),
|
||||
fileRepo () {
|
||||
return this.currentFile && this.currentFile.repo
|
||||
},
|
||||
fileName () {
|
||||
return this.currentFile && this.currentFile.name
|
||||
},
|
||||
filePath () {
|
||||
return this.currentFile && this.currentFile.path
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value () {
|
||||
currentContent () {
|
||||
this.render()
|
||||
},
|
||||
filePath () {
|
||||
@@ -364,6 +379,7 @@ button {
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
transition: all .3s ease-in-out;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
@@ -372,13 +388,13 @@ button:hover {
|
||||
|
||||
.action-bar {
|
||||
position: fixed;
|
||||
width: 43vw;
|
||||
padding: 0 20px;
|
||||
right: 0;
|
||||
width: 27vw;
|
||||
padding: 0;
|
||||
right: 20px;
|
||||
max-width: 980px;
|
||||
box-sizing: border-box;
|
||||
z-index: 1000;
|
||||
margin-top: -2.2em;
|
||||
margin-top: -1.8em;
|
||||
}
|
||||
|
||||
.action-bar > div {
|
||||
@@ -449,15 +465,11 @@ button:hover {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.scroll-to-top.show-xterm {
|
||||
bottom: 45vh;
|
||||
}
|
||||
|
||||
.scroll-to-top {
|
||||
user-select: none;
|
||||
position: fixed;
|
||||
right: 2em;
|
||||
bottom: 40px;
|
||||
/* bottom: 40px; */
|
||||
background: #333030;
|
||||
color: #ccc;
|
||||
font-size: 14px;
|
||||
@@ -516,6 +528,14 @@ button:hover {
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.view {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 40px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.view .markdown-body hr {
|
||||
border-bottom: 1px solid;
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
<template>
|
||||
<div @keydown.tab.stop="switchTab()" @keydown.enter="chooseItem()" @keydown.up="selectItem(-1)" @keydown.down="selectItem(1)" class="filter" @click.stop>
|
||||
<div class="tab">
|
||||
<div @click="switchTab('file')" :class="{selected: currentTab === 'file'}">快速跳转</div>
|
||||
<div @click="switchTab('search')" :class="{selected: currentTab === 'search'}">搜索内容</div>
|
||||
<!-- <div>标签</div> -->
|
||||
</div>
|
||||
<input ref="input" @blur="$emit('close')" v-model="searchText" type="text" class="input" @keydown.tab.prevent @keydown.up.prevent @keydown.down.prevent autofocus>
|
||||
<ul ref="result" class="result">
|
||||
<li v-if="list === null">加载中……</li>
|
||||
<template v-else>
|
||||
<li
|
||||
v-for="item in list"
|
||||
:key="item.path"
|
||||
:class="{selected: selected === item}"
|
||||
@click="chooseItem(item)">
|
||||
<span ref="fileName">
|
||||
{{item.name}}
|
||||
</span>
|
||||
<span ref="filePath" class="path">
|
||||
{{item.path.substr(0, item.path.lastIndexOf('/'))}}
|
||||
</span>
|
||||
</li>
|
||||
<li v-if="list.length < 1">无结果</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import _ from 'lodash'
|
||||
import { mapState } from 'vuex'
|
||||
import file from '@/lib/file'
|
||||
import fuzzyMatch from '@/lib/fuzzyMatch'
|
||||
|
||||
export default {
|
||||
name: 'quick-open',
|
||||
components: {},
|
||||
data () {
|
||||
return {
|
||||
selected: null,
|
||||
searchText: '',
|
||||
currentTab: 'file',
|
||||
list: [],
|
||||
lastFetchTime: 0
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.searchWithDebounce = _.debounce((text, call) => {
|
||||
if (this.repo && text.trim()) {
|
||||
const fetchTime = new Date().getTime()
|
||||
this.lastFetchTime = fetchTime
|
||||
file.search(this.repo, text.trim(), data => {
|
||||
// 总是保证最后的搜索结果出现在列表
|
||||
if (fetchTime >= this.lastFetchTime) {
|
||||
call(data)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.list = []
|
||||
}
|
||||
}, 500)
|
||||
},
|
||||
mounted () {
|
||||
this.$refs.input.focus()
|
||||
this.updateDataSource()
|
||||
},
|
||||
methods: {
|
||||
travelFiles (tree) {
|
||||
let tmp = []
|
||||
|
||||
tree.forEach(node => {
|
||||
if (node.type === 'file' && node.path.endsWith('.md')) {
|
||||
tmp.push(node)
|
||||
}
|
||||
|
||||
if (Array.isArray(node.children)) {
|
||||
tmp = tmp.concat(this.travelFiles(node.children))
|
||||
}
|
||||
})
|
||||
|
||||
return tmp
|
||||
},
|
||||
highlightText (search) {
|
||||
if (this.$refs.fileName && this.$refs.filePath) {
|
||||
search = search.toLowerCase()
|
||||
|
||||
const openF = '(#$*B'
|
||||
const closeF = '#$*B)'
|
||||
|
||||
const escape = function (s) {
|
||||
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
||||
}
|
||||
|
||||
const openR = new RegExp(escape(openF), 'g')
|
||||
const closeR = new RegExp(escape(closeF), 'g')
|
||||
|
||||
this.$refs.fileName.concat(this.$refs.filePath).forEach(function (it) {
|
||||
let text = ''
|
||||
|
||||
it.innerText.toLowerCase().split('').forEach(char => {
|
||||
if (search.indexOf(char) > -1) {
|
||||
text += `${openF}${char}${closeF}`
|
||||
} else {
|
||||
text += char
|
||||
}
|
||||
})
|
||||
|
||||
it.innerText = text
|
||||
|
||||
it.innerHTML = it.innerHTML.replace(openR, '<b>').replace(closeR, '</b>')
|
||||
})
|
||||
}
|
||||
},
|
||||
filterFiles (files, search) {
|
||||
const tmp = []
|
||||
|
||||
files.forEach(x => {
|
||||
const result = fuzzyMatch(search, x.path)
|
||||
|
||||
if (result.matched) {
|
||||
tmp.push({ ...x })
|
||||
}
|
||||
})
|
||||
|
||||
return tmp.sort((a, b) => b.score - a.score)
|
||||
},
|
||||
updateDataSource () {
|
||||
if (this.currentTab === 'file') {
|
||||
this.list = this.filterFiles(this.files, this.searchText.trim())
|
||||
this.sortList()
|
||||
} else {
|
||||
this.list = null
|
||||
this.searchWithDebounce(this.searchText.trim(), data => {
|
||||
this.list = data
|
||||
this.sortList()
|
||||
})
|
||||
}
|
||||
|
||||
this.$nextTick(this.highlightText(this.searchText.trim()))
|
||||
},
|
||||
updateSelected (item = null) {
|
||||
if (this.list === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (item) {
|
||||
this.selected = item
|
||||
} else {
|
||||
this.selected = this.list.length > 0 ? this.list[0] : null
|
||||
}
|
||||
|
||||
this.$nextTick(() => {
|
||||
const li = this.$refs.result.querySelector('li.selected')
|
||||
if (li) {
|
||||
li.scrollIntoViewIfNeeded()
|
||||
}
|
||||
})
|
||||
},
|
||||
selectItem (inc) {
|
||||
if (this.list.length < 1) {
|
||||
this.updateSelected()
|
||||
return
|
||||
}
|
||||
|
||||
const currentIndex = this.list.findIndex(x => this.selected === x)
|
||||
|
||||
let index = currentIndex + inc
|
||||
if (index > this.list.length - 1) {
|
||||
index = 0
|
||||
} else if (index < 0) {
|
||||
index = this.list.length - 1
|
||||
}
|
||||
|
||||
this.updateSelected(this.list[index])
|
||||
},
|
||||
chooseItem (item = null) {
|
||||
const file = item || this.selected
|
||||
if (file) {
|
||||
this.$emit('choose-file', file)
|
||||
}
|
||||
},
|
||||
switchTab (tab = null) {
|
||||
if (tab) {
|
||||
this.currentTab = tab
|
||||
return
|
||||
}
|
||||
|
||||
if (this.currentTab === 'file') {
|
||||
this.currentTab = 'search'
|
||||
} else if (this.currentTab === 'search') {
|
||||
this.currentTab = 'file'
|
||||
}
|
||||
},
|
||||
sortList () {
|
||||
if (this.list === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const map = (this.recentOpenTime || {})
|
||||
|
||||
this.list.sort((a, b) => {
|
||||
const at = map[`${a.repo}|${a.path}`] || 0
|
||||
const bt = map[`${a.repo}|${b.path}`] || 0
|
||||
|
||||
return bt - at
|
||||
})
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list () {
|
||||
this.updateSelected()
|
||||
},
|
||||
searchText () {
|
||||
this.updateDataSource()
|
||||
},
|
||||
currentTab () {
|
||||
this.list = null
|
||||
this.$refs.input.focus()
|
||||
this.updateDataSource()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['currentRepo', 'recentOpenTime', 'tree']),
|
||||
files () {
|
||||
return this.travelFiles(this.tree)
|
||||
},
|
||||
repo () {
|
||||
return this.currentRepo && this.currentRepo.name
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter {
|
||||
width: 600px;
|
||||
background: #403e3e;
|
||||
margin: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
font-size: 18px;
|
||||
line-height: 1.4em;
|
||||
padding: 6px;
|
||||
box-sizing: border-box;
|
||||
background: #333030;
|
||||
color: #ddd;
|
||||
transition: all .1s ease-in-out;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
background: #242222;
|
||||
}
|
||||
|
||||
.result {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
.result li {
|
||||
color: #999;
|
||||
line-height: 1.5em;
|
||||
font-size: 18px;
|
||||
padding: 2px;
|
||||
transition: all .1s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.result li.selected,
|
||||
.result li:hover {
|
||||
padding: 2px 6px;
|
||||
background: #333030;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.result li span {
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
.result li span.path {
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
padding-left: .3em;
|
||||
}
|
||||
|
||||
.result li span.path /deep/ b {
|
||||
color: #ababab;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.result li /deep/ b {
|
||||
color: #c6d2ca;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
.tab > div {
|
||||
flex: auto;
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
font-size: 12px;
|
||||
padding: 4px 0;
|
||||
background: #403e3e;
|
||||
cursor: pointer;
|
||||
transition: all .2s ease-in-out;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.tab > div:hover {
|
||||
background: #444141;
|
||||
}
|
||||
|
||||
.tab > div.selected {
|
||||
background: #333030;
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +1,15 @@
|
||||
<template>
|
||||
<div class="repository-switch">
|
||||
<div class="current" v-if="current">仓库:{{current}}</div>
|
||||
<div class="current" v-if="currentRepo">仓库:{{currentRepo.name}}</div>
|
||||
<div class="current" v-else>未选择仓库</div>
|
||||
<ul class="list">
|
||||
<li v-for="(path, name) in repositories" :key="name" :title="path" @click="choose(name)">{{name}}</li>
|
||||
<li v-for="(path, name) in repositories" :key="name" :title="path" @click="choose({name, path})">{{name}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import file from '../file'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'repository-switch',
|
||||
@@ -19,50 +19,33 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
current: null,
|
||||
repositories: {}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.choose(this.getRepo())
|
||||
this.fetchRepositories()
|
||||
this.$store.dispatch('app/fetchRepositories')
|
||||
},
|
||||
beforeDestroy () {
|
||||
},
|
||||
methods: {
|
||||
fetchRepositories () {
|
||||
file.fetchRepositories(data => {
|
||||
this.repositories = data
|
||||
const keys = Object.keys(data)
|
||||
|
||||
if (keys.length > 0 && !keys.includes(this.current)) {
|
||||
this.choose(keys[0])
|
||||
}
|
||||
})
|
||||
},
|
||||
choose (name) {
|
||||
this.current = name
|
||||
this.storeRepo(this.current)
|
||||
},
|
||||
storeRepo (name) {
|
||||
window.localStorage['repository'] = name
|
||||
window.localStorage['repository_path'] = this.repositories[name]
|
||||
},
|
||||
getRepo () {
|
||||
return window.localStorage['repository'] || null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
current (val) {
|
||||
if (val) {
|
||||
this.$bus.emit('switch-repository', val)
|
||||
this.storeRepo(this.current)
|
||||
choose (repo) {
|
||||
if (repo.name !== this.currentRepo.name) {
|
||||
this.$store.commit('app/setCurrentRepo', repo)
|
||||
}
|
||||
},
|
||||
repositories () {
|
||||
this.storeRepo(this.current)
|
||||
},
|
||||
watch: {
|
||||
repositories (val) {
|
||||
const keys = Object.keys(val)
|
||||
if (!this.currentRepo || keys.indexOf(this.currentRepo.name) < 0) {
|
||||
if (keys.length > 0) {
|
||||
const name = keys[0]
|
||||
this.$store.commit('app/setCurrentRepo', { name, path: val[name] })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['currentRepo', 'repositories']),
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -17,38 +17,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import RepositorySwitch from './RepositorySwitch'
|
||||
|
||||
export default {
|
||||
name: 'status-bar',
|
||||
components: { RepositorySwitch },
|
||||
props: {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
documentInfo: {
|
||||
textLength: 0,
|
||||
selectedLength: 0,
|
||||
lineCount: 0,
|
||||
line: 0,
|
||||
column: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.keydownHandler, true)
|
||||
this.$bus.on('change-document', this.handleChangeDocument)
|
||||
},
|
||||
mounted () {
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keydownHandler)
|
||||
this.$bus.off('change-document', this.handleChangeDocument)
|
||||
},
|
||||
methods: {
|
||||
handleChangeDocument (data) {
|
||||
this.documentInfo = data
|
||||
},
|
||||
toggleView () {
|
||||
this.$bus.emit('toggle-view')
|
||||
},
|
||||
@@ -56,7 +37,7 @@ export default {
|
||||
this.$bus.emit('toggle-xterm')
|
||||
},
|
||||
toggleReadme () {
|
||||
this.$bus.emit('toggle-readme')
|
||||
this.$store.dispatch('app/showReadme')
|
||||
},
|
||||
toggleWrap () {
|
||||
this.$bus.emit('editor-toggle-wrap')
|
||||
@@ -90,6 +71,7 @@ export default {
|
||||
watch: {
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['documentInfo'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -110,6 +92,7 @@ export default {
|
||||
background: #4e4e4e;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.document-info > span {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="title-bar" :style="saved ? '' : 'background: orange'">
|
||||
<h4 class="title"> {{statusText}}</h4>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'title-bar',
|
||||
mounted () {
|
||||
window.onbeforeunload = () => {
|
||||
return !this.saved || null
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.onbeforeunload = null
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['currentFile', 'savedAt', 'previousContent', 'currentContent']),
|
||||
saved () {
|
||||
return this.previousContent === this.currentContent
|
||||
},
|
||||
status () {
|
||||
if (this.savedAt === null && this.currentFile) {
|
||||
return '加载完毕'
|
||||
} else if (this.savedAt) {
|
||||
return '保存于:' + this.savedAt.toLocaleString()
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
statusText () {
|
||||
const file = this.currentFile
|
||||
if (file) {
|
||||
if (file.repo === '__readme__') {
|
||||
return file.title
|
||||
}
|
||||
|
||||
if (file.path && file.repo) {
|
||||
return `${file.path}-${this.status} [${file.repo}]`
|
||||
} else {
|
||||
return file.name
|
||||
}
|
||||
} else {
|
||||
return '未打开文件'
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
statusText: {
|
||||
immediate: true,
|
||||
handler (val) {
|
||||
document.title = val
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.title-bar {
|
||||
background: #4e4e4e;
|
||||
color: #eee;
|
||||
height: 100%;
|
||||
transition: all .3s ease-in-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
@@ -1,171 +1,51 @@
|
||||
<template>
|
||||
<aside class="side" @dblclick="init()" title="双击此处刷新目录树">
|
||||
<aside class="side" @dblclick="refresh" title="双击此处刷新目录树">
|
||||
<div class="loading" v-if="tree === null"> 加载中 </div>
|
||||
<template v-else>
|
||||
<TreeNode
|
||||
v-for="item in tree"
|
||||
:slected-file="file"
|
||||
:item="item"
|
||||
:key="item.path"
|
||||
@move="onMove"
|
||||
@change="change"
|
||||
@select="handleSelect"
|
||||
@delete="onDelete" />
|
||||
<transition name="fade">
|
||||
<div v-if="showFilter" class="filter-wrapper" @click="showFilter = false">
|
||||
<XFilter @choose-file="showFilter" :repo="repo" :files="files" />
|
||||
</div>
|
||||
</transition>
|
||||
<TreeNode v-for="item in tree" :item="item" :key="item.path" />
|
||||
</template>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import File from '../file'
|
||||
import { mapState } from 'vuex'
|
||||
import TreeNode from './TreeNode'
|
||||
import XFilter from './Filter'
|
||||
|
||||
export default {
|
||||
name: 'tree',
|
||||
components: { TreeNode, XFilter },
|
||||
components: { TreeNode },
|
||||
data () {
|
||||
return {
|
||||
repo: null,
|
||||
tree: null,
|
||||
file: null,
|
||||
showFilter: false,
|
||||
editorReady: false
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.keydownHandler, true)
|
||||
this.$bus.on('switch-repository', this.init)
|
||||
this.$bus.on('editor-ready', this.handleReady)
|
||||
this.$bus.on('tree-refresh', this.change)
|
||||
this.$bus.on('file-created', this.refresh)
|
||||
this.$bus.on('file-moved', this.refresh)
|
||||
this.$bus.on('file-deleted', this.refresh)
|
||||
this.$bus.on('file-uploaded', this.refresh)
|
||||
this.refresh()
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.keydownHandler)
|
||||
this.$bus.off('switch-repository', this.init)
|
||||
this.$bus.off('editor-ready', this.handleReady)
|
||||
this.$bus.off('tree-refresh', this.change)
|
||||
this.$bus.off('file-created', this.refresh)
|
||||
this.$bus.off('file-moved', this.refresh)
|
||||
this.$bus.off('file-deleted', this.refresh)
|
||||
this.$bus.off('file-uploaded', this.refresh)
|
||||
},
|
||||
methods: {
|
||||
closeCurrentFile () {
|
||||
this.file = null
|
||||
refresh () {
|
||||
this.$store.dispatch('app/fetchTree', this.currentRepo)
|
||||
},
|
||||
handleSelect (file) {
|
||||
if (this.editorReady) {
|
||||
this.file = file
|
||||
this.updateRecentOpenTime(this.file)
|
||||
}
|
||||
},
|
||||
handleReady () {
|
||||
this.editorReady = true
|
||||
|
||||
// 第一次打开此程序
|
||||
if (!Object.keys(window.localStorage).find(x => x.endsWith('_open_time'))) {
|
||||
this.$bus.$emit('toggle-readme')
|
||||
}
|
||||
},
|
||||
init (repo = null, path = null) {
|
||||
if (repo) {
|
||||
this.repo = repo
|
||||
this.tree = null
|
||||
this.file = null
|
||||
}
|
||||
|
||||
File.tree(this.repo, tree => {
|
||||
this.tree = tree
|
||||
|
||||
if (path) {
|
||||
this.$nextTick(() => {
|
||||
this.$bus.emit('choose-file', { path })
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
onDelete (path) {
|
||||
// 删除了正在编辑的文件或者其父目录
|
||||
if (this.file && this.file.path.startsWith(path)) {
|
||||
this.file = null
|
||||
}
|
||||
|
||||
this.init()
|
||||
},
|
||||
onMove ({ oldPath, newPath }) {
|
||||
// 移动了正在编辑的文件或者其父目录
|
||||
if (this.file && this.file.path.startsWith(oldPath)) {
|
||||
this.file = null
|
||||
this.init(null, newPath)
|
||||
} else {
|
||||
this.init()
|
||||
}
|
||||
},
|
||||
change (path) {
|
||||
this.init(null, path)
|
||||
},
|
||||
keydownHandler (e) {
|
||||
if (e.key === 'i' && e.ctrlKey && e.altKey) {
|
||||
this.showFilter = f => {
|
||||
if (this.file) {
|
||||
const relativePath = f.path.replace(this.file.path.substr(0, this.file.path.lastIndexOf('/')), '.')
|
||||
this.$bus.emit('editor-insert-value', `[${f.name.replace(/\.[^.]$/, '')}](${encodeURI(relativePath)})`)
|
||||
}
|
||||
this.showFilter = false
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
} else if (e.key === 'p' && e.ctrlKey) {
|
||||
this.showFilter = f => {
|
||||
this.$bus.emit('choose-file', f)
|
||||
this.showFilter = false
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
} else if (e.key === 'Escape' && this.showFilter) {
|
||||
this.showFilter = false
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}
|
||||
},
|
||||
travelFiles (tree) {
|
||||
let tmp = []
|
||||
|
||||
tree.forEach(node => {
|
||||
if (node.type === 'file' && node.path.endsWith('.md')) {
|
||||
tmp.push(node)
|
||||
}
|
||||
|
||||
if (Array.isArray(node.children)) {
|
||||
tmp = tmp.concat(this.travelFiles(node.children))
|
||||
}
|
||||
})
|
||||
|
||||
return tmp
|
||||
},
|
||||
updateRecentOpenTime (file) {
|
||||
const json = window.localStorage[`${file.repo}_open_time`] || '{}'
|
||||
|
||||
let map = {}
|
||||
try {
|
||||
map = JSON.parse(json)
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
map[file.path] = new Date().getTime()
|
||||
|
||||
window.localStorage[`${file.repo}_open_time`] = JSON.stringify(map)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
file (f) {
|
||||
this.$emit('input', f)
|
||||
},
|
||||
currentRepo () {
|
||||
this.refresh()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
files () {
|
||||
return this.travelFiles(this.tree)
|
||||
}
|
||||
...mapState('app', ['currentRepo', 'tree']),
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -173,25 +53,10 @@ export default {
|
||||
<style scoped>
|
||||
.side {
|
||||
color: #ddd;
|
||||
background: #282a2b;
|
||||
}
|
||||
|
||||
.filter-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, .2);
|
||||
z-index: 99999;
|
||||
padding-top: 4em;
|
||||
}
|
||||
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.loading {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="tree-node">
|
||||
<details class="name" :title="item.name + '\n\n' + dirTitle" ref="dir" :open="item.path === '/'" v-if="item.type === 'dir'">
|
||||
<details v-if="item.type === 'dir'" class="name" :title="item.name + '\n\n' + dirTitle" ref="dir" :open="item.path === '/'">
|
||||
<summary
|
||||
class="dir-label"
|
||||
:style="{background: selected ? '#313131' : 'none'}"
|
||||
@@ -9,19 +9,12 @@
|
||||
@click.ctrl.alt.exact.prevent="revealInXterminal(item)"
|
||||
@contextmenu.ctrl.prevent="renameFile"
|
||||
@contextmenu.shift.prevent="deleteFile"> {{ item.name }} <span class="count">({{item.children.length}})</span> </summary>
|
||||
<tree-node
|
||||
v-for="x in item.children"
|
||||
:key="x.path" :item="x"
|
||||
:slected-file="slectedFile"
|
||||
@select="select"
|
||||
@move="p => $emit('move', p)"
|
||||
@change="p => $emit('change', p)"
|
||||
@delete="p => $emit('delete', p)"></tree-node>
|
||||
<tree-node v-for="x in item.children" :key="x.path" :item="x"></tree-node>
|
||||
</details>
|
||||
<div
|
||||
v-else
|
||||
class="name"
|
||||
:title="item.name + '\n\n' + fileTitle"
|
||||
v-else
|
||||
@click="select(item)"
|
||||
@dblclick.ctrl.exact="revealInExplorer()"
|
||||
@contextmenu.ctrl.prevent="renameFile"
|
||||
@@ -31,20 +24,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import File from '../file'
|
||||
import { mapState } from 'vuex'
|
||||
import File from '@/lib/file'
|
||||
|
||||
export default {
|
||||
name: 'tree-node',
|
||||
props: {
|
||||
item: Object,
|
||||
slectedFile: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
selected: false,
|
||||
dirTitle: [
|
||||
'"双击" 创建新文件',
|
||||
'"Ctrl + 右键" 重命名目录',
|
||||
@@ -60,52 +49,28 @@ export default {
|
||||
].join('\n')
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$bus.on('editor-ready', this.handleReady)
|
||||
this.$bus.on('choose-file', this.handleChooseFile)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$bus.off('editor-ready', this.handleReady)
|
||||
this.$bus.off('choose-file', this.handleChooseFile)
|
||||
},
|
||||
mounted () {
|
||||
this.handleReady()
|
||||
},
|
||||
methods: {
|
||||
handleReady () {
|
||||
this.chooseFile(this.getSelectedFilePath())
|
||||
},
|
||||
handleChooseFile (file) {
|
||||
this.chooseFile(file.path)
|
||||
},
|
||||
chooseFile (filePath) {
|
||||
if (this.item.type === 'dir' && filePath.startsWith(this.item.path)) {
|
||||
updateOpenStatus () {
|
||||
if (this.currentFile && this.item.type === 'dir' && this.currentFile.path.startsWith(this.item.path + '/')) {
|
||||
this.$refs.dir.open = true
|
||||
} else if (filePath === this.item.path) {
|
||||
this.select(this.item)
|
||||
}
|
||||
},
|
||||
select (f) {
|
||||
if (f.name.endsWith('.md')) {
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
this.selected = true
|
||||
})
|
||||
})
|
||||
|
||||
this.$emit('select', f)
|
||||
this.storeSelectedFilePath(f.path)
|
||||
} else {
|
||||
window.open(`api/attachment/${encodeURIComponent(f.name)}?repo=${f.repo}&path=${encodeURIComponent(f.path)}`)
|
||||
select (item) {
|
||||
if (item.type !== 'dir') {
|
||||
if (item.name.endsWith('.md')) {
|
||||
this.$store.commit('app/setCurrentFile', item)
|
||||
} else {
|
||||
window.open(`api/attachment/${encodeURIComponent(item.name)}?repo=${item.repo}&path=${encodeURIComponent(item.path)}`)
|
||||
}
|
||||
}
|
||||
},
|
||||
revealInExplorer () {
|
||||
File.openInOS(this.item.repo, this.item.path)
|
||||
},
|
||||
revealInXterminal (item) {
|
||||
const path = window.localStorage['repository_path'] ? window.localStorage['repository_path'] + item.path : '~'
|
||||
const path = this.currentRepo ? this.currentRepo.path + item.path : '~'
|
||||
|
||||
this.$bus.emit('run-in-terminal', `cd '${path.replace('\'', '\\\'')}'`)
|
||||
this.$bus.emit('xterm-run', `cd '${path.replace('\'', '\\\'')}'`)
|
||||
},
|
||||
createFile () {
|
||||
let filename = window.prompt(`[${this.item.path}] 文件名`, 'new.md')
|
||||
@@ -120,7 +85,9 @@ export default {
|
||||
|
||||
const path = this.item.path.replace(/\/$/, '') + '/' + filename
|
||||
File.write(this.item.repo, path, `# ${filename.replace(/\.md$/i, '')}\n`, 'new', () => {
|
||||
this.$emit('change', path)
|
||||
const newFile = { name: filename, path, repo: this.item.repo, type: this.item.type }
|
||||
this.$bus.emit('file-created', newFile)
|
||||
this.$store.commit('app/setCurrentFile', newFile)
|
||||
}, e => {
|
||||
alert(e.message)
|
||||
})
|
||||
@@ -133,26 +100,33 @@ export default {
|
||||
}
|
||||
|
||||
File.move(this.item.repo, this.item.path, newPath, () => {
|
||||
this.$emit('move', { oldPath: this.item.path, newPath })
|
||||
const newFile = { name: newPath.substr(newPath.lastIndexOf('/') + 1), path: newPath, repo: this.item.repo, type: this.item.type }
|
||||
this.$bus.emit('file-moved', newFile)
|
||||
if (this.currentFile.repo === this.item.repo && this.currentFile.path === this.item.path) {
|
||||
this.$store.commit('app/setCurrentFile', newFile)
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteFile () {
|
||||
if (window.confirm(`确定要删除 [${this.item.path}] 吗?`)) {
|
||||
File.delete(this.item.repo, this.item.path, () => {
|
||||
this.$emit('delete', this.item.path)
|
||||
this.$bus.emit('file-deleted', this.item)
|
||||
if (this.currentFile.repo === this.item.repo && this.currentFile.path === this.item.path) {
|
||||
this.$store.commit('app/setCurrentFile', null)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
storeSelectedFilePath (path) {
|
||||
window.localStorage[`selectedFile_${this.item.repo}`] = path
|
||||
},
|
||||
getSelectedFilePath () {
|
||||
return window.localStorage[`selectedFile_${this.item.repo}`] || ''
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['currentFile', 'currentRepo']),
|
||||
selected () {
|
||||
return this.currentFile && this.currentFile.path === this.item.path && this.currentFile.repo === this.item.repo
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
slectedFile () {
|
||||
this.selected = false
|
||||
currentFile () {
|
||||
this.updateOpenStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,12 +19,14 @@ export default {
|
||||
name: 'xterm',
|
||||
created () {
|
||||
window.runInXterm = this.runInXterm
|
||||
this.$bus.on('run-in-terminal', this.handleRunInXterm)
|
||||
this.$bus.on('xterm-run', this.handleRunInXterm)
|
||||
this.$bus.on('xterm-init', this.init)
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('resize', this.fitXterm)
|
||||
window.runInXterm = null
|
||||
this.$bus.off('run-in-terminal', this.handleRunInXterm)
|
||||
this.$bus.off('xterm-init', this.init)
|
||||
this.$bus.off('xterm-run', this.handleRunInXterm)
|
||||
this.$bus.off('resize', this.handleRunInXterm)
|
||||
},
|
||||
methods: {
|
||||
init () {
|
||||
@@ -43,7 +45,7 @@ export default {
|
||||
|
||||
xterm.open(this.$refs.xterm)
|
||||
xterm.fit()
|
||||
window.addEventListener('resize', this.fitXterm)
|
||||
this.$bus.on('resize', this.fitXterm)
|
||||
}
|
||||
|
||||
if (!socket) {
|
||||
@@ -129,5 +131,7 @@ export default {
|
||||
background: rgb(29, 31, 33);
|
||||
border: 1px solid rgb(92, 91, 100);
|
||||
flex: 0 0 auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
const get = function (key, defaultValue) {
|
||||
try {
|
||||
return typeof window.localStorage[key] === 'undefined' ? defaultValue : JSON.parse(window.localStorage[key])
|
||||
} catch (error) {
|
||||
return defaultStatus
|
||||
}
|
||||
}
|
||||
|
||||
const set = function (key, value) {
|
||||
window.localStorage[key] = JSON.stringify(value)
|
||||
}
|
||||
|
||||
export default {
|
||||
set,
|
||||
get
|
||||
}
|
||||
@@ -52,7 +52,8 @@ const decrypt = content => {
|
||||
|
||||
const oldPasswordHash = {}
|
||||
export default {
|
||||
read: (repo, path, call, ecall) => {
|
||||
read: (file, call, ecall) => {
|
||||
const { path, repo } = file
|
||||
fetch(`/api/file?path=${encodeURIComponent(path)}&repo=${repo}`).then(response => {
|
||||
response.json().then(result => {
|
||||
if (result.status === 'ok') {
|
||||
@@ -95,6 +96,7 @@ export default {
|
||||
} catch (e) {
|
||||
if (ecall) {
|
||||
ecall(e)
|
||||
return
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import store from './store/index'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
|
||||
@@ -1,351 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<header class="header" :style="unsaved ? 'background: orange' : ''">
|
||||
<div>
|
||||
<h4 style="margin: 0;text-align: center"> {{statusText}}</h4>
|
||||
</div>
|
||||
</header>
|
||||
<div style="display: flex; justify-content: space-between;" :class="{'show-view': showView}">
|
||||
<Tree ref="tree" class="tree" v-model="file"></Tree>
|
||||
<div class="content" style="flex-direction: column;">
|
||||
<div :class="{content: true, 'show-xterm': showXterm}">
|
||||
<Editor
|
||||
ref="editor"
|
||||
class="editor"
|
||||
v-model="value"
|
||||
@ready="editorReady"
|
||||
@scroll-line="syncScrollView"
|
||||
@paste-img="pasteImg"
|
||||
@upload-file="uploadFile"
|
||||
@save="saveFile"></Editor>
|
||||
<XView
|
||||
ref="view"
|
||||
class="view"
|
||||
:value="value"
|
||||
:file-name="fileName"
|
||||
:file-path="filePath"
|
||||
:file-repo="fileRepo"
|
||||
:show-xterm="showXterm"
|
||||
@sync-scroll="syncScrollEditor"
|
||||
@switch-todo="switchTodoEditor"></XView>
|
||||
</div>
|
||||
<Xterm ref="xterm" v-show="showXterm" class="xterm"></Xterm>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-bar">
|
||||
<StatusBar></StatusBar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dayjs from 'dayjs'
|
||||
import Editor from '../components/Editor'
|
||||
import XView from '../components/View'
|
||||
import Xterm from '../components/Xterm'
|
||||
import Tree from '../components/Tree'
|
||||
import StatusBar from '../components/StatusBar'
|
||||
import RunPlugin from '../plugins/RunPlugin'
|
||||
import File from '../file'
|
||||
|
||||
export default {
|
||||
name: 'home',
|
||||
components: { XView, Editor, Tree, StatusBar, Xterm },
|
||||
data () {
|
||||
return {
|
||||
status: '请选择文件',
|
||||
value: '',
|
||||
lastSaveContent: '',
|
||||
file: null,
|
||||
oldHash: null,
|
||||
timer: null,
|
||||
showView: true,
|
||||
showXterm: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
RunPlugin.clearCache()
|
||||
this.restartTimer()
|
||||
|
||||
this.$bus.on('toggle-view', this.toggleView)
|
||||
this.$bus.on('toggle-xterm', this.toggleXterm)
|
||||
this.$bus.on('toggle-readme', this.toggleReadme)
|
||||
|
||||
window.onbeforeunload = () => {
|
||||
return this.unsaved || null
|
||||
}
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$bus.off('toggle-view', this.toggleView)
|
||||
this.$bus.off('toggle-xterm', this.toggleXterm)
|
||||
this.$bus.off('toggle-readme', this.toggleReadme)
|
||||
this.clearTimer()
|
||||
},
|
||||
methods: {
|
||||
toggleReadme () {
|
||||
if (this.file && this.file.repo === '__readme__') {
|
||||
this.$refs.tree.closeCurrentFile()
|
||||
this.file = null
|
||||
} else {
|
||||
File.readme(content => {
|
||||
this.file = {
|
||||
repo: '__readme__',
|
||||
title: 'README.md',
|
||||
content
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
toggleView () {
|
||||
this.showView = !this.showView
|
||||
},
|
||||
toggleXterm (flag) {
|
||||
this.showXterm = flag === undefined ? !this.showXterm : !!flag
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.$refs.editor.resize()
|
||||
|
||||
if (this.showXterm) {
|
||||
this.$refs.xterm.init()
|
||||
}
|
||||
})
|
||||
},
|
||||
editorReady () {
|
||||
this.$bus.emit('editor-ready')
|
||||
},
|
||||
clearTimer () {
|
||||
if (this.timer) {
|
||||
window.clearTimeout(this.timer)
|
||||
}
|
||||
},
|
||||
restartTimer () {
|
||||
this.clearTimer()
|
||||
|
||||
if (!(this.file && this.file.repo && this.file.path)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.timer = window.setTimeout(() => {
|
||||
if (!this.file || this.file.path.endsWith('.c.md')) { // 加密文件不自动保存
|
||||
return
|
||||
}
|
||||
|
||||
this.saveFile()
|
||||
}, 2000)
|
||||
},
|
||||
saveFile (f = null) {
|
||||
const file = f || this.file
|
||||
|
||||
if (!(file && file.repo && file.path)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.lastSaveContent === this.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const content = this.value
|
||||
File.write(file.repo, file.path, content, this.oldHash, result => {
|
||||
this.oldHash = result.data
|
||||
this.lastSaveContent = content
|
||||
this.status = '保存于:' + (new Date()).toLocaleString()
|
||||
}, e => {
|
||||
this.file = null
|
||||
alert(e.message)
|
||||
})
|
||||
},
|
||||
pasteImg (file) {
|
||||
File.upload(this.file.repo, this.file.path, file, ({ relativePath }) => {
|
||||
this.$refs.tree.change()
|
||||
this.$refs.editor.insert(`})\n`)
|
||||
})
|
||||
},
|
||||
uploadFile (file) {
|
||||
File.upload(this.file.repo, this.file.path, file, ({ relativePath }) => {
|
||||
this.$refs.tree.change()
|
||||
this.$refs.editor.insert(`附件 [${dayjs().format('YYYY-MM-DD HH:mm')}]:[${file.name} (${(file.size / 1024).toFixed(2)}KiB)](${encodeURI(relativePath).replace('(', '%28').replace(')', '%29')}){class=open target=_blank}\n`)
|
||||
}, `${dayjs().format('YYYYMMDDHHmmss')}.${file.name}`)
|
||||
},
|
||||
switchTodoEditor (line, checked) {
|
||||
this.$refs.editor.switchTodo(line, checked)
|
||||
},
|
||||
syncScrollEditor (line) {
|
||||
this.$refs.editor.revealLine(line)
|
||||
},
|
||||
syncScrollView (line) {
|
||||
this.$refs.view.revealLine(line)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showView () {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.editor.resize()
|
||||
})
|
||||
},
|
||||
value () {
|
||||
this.restartTimer()
|
||||
},
|
||||
file (f, oldf) {
|
||||
this.clearTimer()
|
||||
|
||||
if (oldf && oldf.repo && oldf.path) {
|
||||
this.saveFile(oldf)
|
||||
}
|
||||
|
||||
if (f) {
|
||||
if (f.title) {
|
||||
window.document.title = f.title
|
||||
this.lastSaveContent = f.content
|
||||
this.$refs.editor.setValue(f.content)
|
||||
} else {
|
||||
File.read(f.repo, f.path, (data, hash) => {
|
||||
this.lastSaveContent = data
|
||||
this.$refs.editor.setValue(data)
|
||||
this.oldHash = hash
|
||||
this.status = '加载完毕'
|
||||
window.document.title = f.name
|
||||
}, e => {
|
||||
this.file = null
|
||||
alert(e.message)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
window.document.title = '未打开文件'
|
||||
this.lastSaveContent = '\n'
|
||||
this.$refs.editor.setValue('\n')
|
||||
}
|
||||
|
||||
// 切换文件时候定位到第一行
|
||||
this.$refs.editor.setPosition({ column: 1, lineNumber: 1 })
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
unsaved () {
|
||||
return this.value !== this.lastSaveContent
|
||||
},
|
||||
fileName () {
|
||||
return this.file ? this.file.name : null
|
||||
},
|
||||
filePath () {
|
||||
return this.file ? this.file.path : null
|
||||
},
|
||||
fileRepo () {
|
||||
return this.file ? this.file.repo : null
|
||||
},
|
||||
statusText () {
|
||||
if (this.file) {
|
||||
if (this.file.path && this.file.repo) {
|
||||
return `${this.file.path}-${this.status} [${this.file.repo}]`
|
||||
} else {
|
||||
return this.file.title
|
||||
}
|
||||
} else {
|
||||
return '未打开文件'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@media screen {
|
||||
.tree {
|
||||
height: 95vh;
|
||||
width: 17vw;
|
||||
padding-bottom: 20px;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.show-view .editor {
|
||||
width: 40vw;
|
||||
}
|
||||
|
||||
.editor {
|
||||
/* height: 95vh; */
|
||||
width: 83vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.show-view .view {
|
||||
width: 43vw;
|
||||
min-width: 200px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.view {
|
||||
box-sizing: border-box;
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
padding: 45px;
|
||||
width: 43vw;
|
||||
/* height: 95vh; */
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
height: 95vh;
|
||||
}
|
||||
|
||||
.content.show-xterm {
|
||||
height: 55vh;
|
||||
}
|
||||
|
||||
.xterm {
|
||||
height: 40vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.view {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #4e4e4e;
|
||||
color: #eee;
|
||||
line-height: 5vh;
|
||||
height: 5vh;
|
||||
transition: all .3s ease-in-out;
|
||||
}
|
||||
|
||||
.header > div {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
@media print {
|
||||
.content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.editor, .header, .tree, .status-bar, .xterm {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.view {
|
||||
min-width: auto;
|
||||
max-width: auto;
|
||||
margin: 0 auto;
|
||||
width: auto;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<div>
|
||||
<Layout>
|
||||
<TitleBar slot="header"></TitleBar>
|
||||
<StatusBar slot="footer"></StatusBar>
|
||||
<Tree slot="left"></Tree>
|
||||
<Xterm slot="terminal"></Xterm>
|
||||
<Editor
|
||||
ref="editor"
|
||||
slot="editor"
|
||||
@scroll-line="line => $refs.preview.revealLine(line)" />
|
||||
<Preview
|
||||
ref="preview"
|
||||
slot="preview"
|
||||
@sync-scroll="line => $refs.editor.revealLine(line)"
|
||||
@switch-todo="(line, checked) => $refs.editor.switchTodo(line, checked)" />
|
||||
</Layout>
|
||||
<XFilter />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import Layout from '@/components/Layout'
|
||||
import TitleBar from '@/components/TitleBar'
|
||||
import StatusBar from '@/components/StatusBar'
|
||||
import Tree from '@/components/Tree'
|
||||
import Editor from '@/components/Editor'
|
||||
import Preview from '@/components/Preview'
|
||||
import RunPlugin from '../plugins/RunPlugin'
|
||||
import XFilter from '@/components/Filter'
|
||||
import Xterm from '@/components/Xterm'
|
||||
|
||||
export default {
|
||||
name: 'x-main',
|
||||
components: { Layout, TitleBar, StatusBar, Tree, Editor, Preview, XFilter, Xterm },
|
||||
mounted () {
|
||||
RunPlugin.clearCache()
|
||||
this.$bus.on('editor-ready', this.init)
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$bus.off('editor-ready', this.init)
|
||||
},
|
||||
methods: {
|
||||
init () {
|
||||
if (!this.currentFile) {
|
||||
this.$store.dispatch('app/showReadme')
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState('app', ['currentFile'])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,11 +1,11 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import Home from '../pages/Home'
|
||||
import Main from './pages/Main'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export default new Router({
|
||||
routes: [
|
||||
{ path: '/', name: 'Home', component: Home }
|
||||
{ path: '/', name: 'Main', component: Main }
|
||||
]
|
||||
})
|
||||
@@ -0,0 +1,99 @@
|
||||
import Storage from '@/lib/Storage'
|
||||
import file from '@/lib/file'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: {
|
||||
repositories: {},
|
||||
tree: null,
|
||||
showView: true,
|
||||
showXterm: false,
|
||||
savedAt: null,
|
||||
currentStatus: 0,
|
||||
currentContent: '',
|
||||
previousContent: '',
|
||||
previousHash: '',
|
||||
currentRepo: Storage.get('currentRepo'),
|
||||
currentFile: Storage.get('currentFile'),
|
||||
recentOpenTime: Storage.get('recentOpenTime', {}),
|
||||
documentInfo: {
|
||||
textLength: 0,
|
||||
selectedLength: 0,
|
||||
lineCount: 0,
|
||||
line: 0,
|
||||
column: 0
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setRepositories (state, data) {
|
||||
state.repositories = data
|
||||
},
|
||||
setTree (state, data) {
|
||||
state.tree = data
|
||||
},
|
||||
setShowView (state, data) {
|
||||
state.showView = data
|
||||
},
|
||||
setShowXterm (state, data) {
|
||||
state.showXterm = data
|
||||
},
|
||||
setPreviousHash (state, data) {
|
||||
state.previousHash = data
|
||||
},
|
||||
setPreviousContent (state, data) {
|
||||
state.previousContent = data
|
||||
},
|
||||
setSavedAt (state, data) {
|
||||
state.savedAt = data
|
||||
},
|
||||
setDocumentInfo (state, data) {
|
||||
state.documentInfo = data
|
||||
},
|
||||
setCurrentContent (state, data) {
|
||||
state.currentContent = data
|
||||
},
|
||||
setCurrentRepo (state, data) {
|
||||
state.currentRepo = data
|
||||
Storage.set('currentRepo', data)
|
||||
},
|
||||
setCurrentFile (state, data) {
|
||||
state.currentFile = data
|
||||
Storage.set('currentFile', data)
|
||||
|
||||
if (data) {
|
||||
state.recentOpenTime = { ...(state.recentOpenTime || {}), [`${data.repo}|${data.path}`]: new Date().valueOf() }
|
||||
Storage.set('recentOpenTime', state.recentOpenTime)
|
||||
}
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
},
|
||||
actions: {
|
||||
fetchRepositories ({ commit }) {
|
||||
file.fetchRepositories(data => {
|
||||
commit('setRepositories', data)
|
||||
})
|
||||
},
|
||||
fetchTree ({ commit }, repo) {
|
||||
if (!repo) {
|
||||
console.warn('未选择仓库')
|
||||
return
|
||||
}
|
||||
|
||||
file.tree(repo.name, tree => {
|
||||
commit('setTree', tree)
|
||||
})
|
||||
},
|
||||
showReadme ({ commit }) {
|
||||
file.readme(content => {
|
||||
commit('setCurrentFile', {
|
||||
repo: '__readme__',
|
||||
title: 'README.md',
|
||||
name: 'README.md',
|
||||
path: '/README.md',
|
||||
content
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import app from './app'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
strict: process.env.NODE_ENV !== 'production',
|
||||
modules: {
|
||||
app,
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
devServer: {
|
||||
port: 8066,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3000'
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"systemParams": "linux-x64-64",
|
||||
"modulesFolders": [],
|
||||
"flags": [],
|
||||
"linkedModules": [],
|
||||
"topLevelPatterns": [],
|
||||
"lockfileEntries": {},
|
||||
"files": [],
|
||||
"artifacts": {}
|
||||
}
|
||||
Reference in New Issue
Block a user