Merge branch 'release/1.24.0'

This commit is contained in:
purocean
2019-07-31 15:39:37 +08:00
34 changed files with 2411 additions and 1206 deletions
+6 -1
View File
@@ -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`
+11
View File
@@ -0,0 +1,11 @@
# Yank-Note 2.0 计划
+ [ ] 项目更名为 Yank-Note
+ [ ] 可以使用 WebDAV 协议作为文件通信协议
+ [ ] 使用 Electron 构建为桌面应用
+ [ ] 前端界面可以调整尺寸
+ [ ] 支持打开多个文件
+ [ ] 支持使用快捷键打开常用文档
+ [ ] 拥有配置界面
+ [ ] 可以自定义快捷键
+ [ ] 暴露 api 接口可以在文档中嵌入轻量应用,以便构建一些小工具
+1
View File
@@ -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',
+12
View 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
+2
View File
@@ -0,0 +1,2 @@
node_modules
dist
+12
View File
@@ -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"
}
}
+51
View File
@@ -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()
}
})
+12
View File
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"noImplicitAny": true,
"outDir": "./dist",
"sourceMap": true
},
"include": [
"src/**/*"
]
}
+865
View File
@@ -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"
+1
View File
@@ -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
View File
@@ -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(`![图片](${encodeURI(relativePath)})\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>
+42 -282
View File
@@ -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>
+215
View File
@@ -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>
+278
View File
@@ -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;
}
+329
View File
@@ -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>
+18 -35
View File
@@ -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>
+4 -21
View File
@@ -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 {
+78
View File
@@ -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>
+22 -157
View File
@@ -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 {
+35 -61
View File
@@ -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()
}
}
}
+8 -4
View File
@@ -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>
+16
View File
@@ -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 -1
View File
@@ -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
-351
View File
@@ -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(`![图片](${encodeURI(relativePath)})\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>
+55
View File
@@ -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 }
]
})
+99
View File
@@ -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
})
})
}
}
}
+13
View File
@@ -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
View File
@@ -1,5 +1,6 @@
module.exports = {
devServer: {
port: 8066,
proxy: {
'/api': {
target: 'http://localhost:3000'
+10
View File
@@ -0,0 +1,10 @@
{
"systemParams": "linux-x64-64",
"modulesFolders": [],
"flags": [],
"linkedModules": [],
"topLevelPatterns": [],
"lockfileEntries": {},
"files": [],
"artifacts": {}
}
+4
View File
@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1