diff --git a/.eslintrc.js b/.eslintrc.js
index e0f9900..1e529a6 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -20,7 +20,8 @@ module.exports = {
'no-async-promise-executor': 'off',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
- '@typescript-eslint/indent': ['error', 2]
+ '@typescript-eslint/indent': ['error', 2],
+ 'vue/no-v-html': 'off'
},
parserOptions: {
parser: '@typescript-eslint/parser'
diff --git a/README.md b/README.md
index 1200aa6..983eb5a 100644
--- a/README.md
+++ b/README.md
@@ -19,7 +19,8 @@
- 保留了PicGo的所有功能,兼容已有的PicGo插件系统,包括和typora、obsidian等的搭配
- 相册中可同步删除云端图片
-- 支持管理所有图床,可以在线进行云端目录查看、文件搜索、批量上传、批量下载、删除文件和图片预览等
+- 支持管理所有图床,可以在线进行云端目录查看、文件搜索、批量上传、批量下载、删除文件等
+- 支持预览多种格式的文件,包括图片、视频、纯文本文件和markdown文件等,具体支持的格式请参考[支持的文件格式列表](https://github.com/Kuingsmile/PicList/supported_format.md)
- 管理界面使用内置数据库缓存目录,加速目录加载速度
- 对于私有存储桶等支持复制预签名链接进行分享
- 优化了PicGo的界面,解锁了窗口大小限制,同时美化了部分界面布局
diff --git a/package.json b/package.json
index 1307960..4e10631 100644
--- a/package.json
+++ b/package.json
@@ -16,18 +16,21 @@
"postuninstall": "electron-builder install-app-deps",
"release": "vue-cli-service electron:build --publish always",
"upload-dist": "node ./scripts/upload-dist-to-r2.js",
- "link": "node ./scripts/cos-link.js"
+ "link": "node ./scripts/link.js"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.272.0",
"@aws-sdk/lib-storage": "^3.272.0",
"@aws-sdk/s3-request-presigner": "^3.272.0",
"@element-plus/icons-vue": "^2.0.10",
+ "@highlightjs/vue-plugin": "^2.1.0",
"@imengyu/vue3-context-menu": "^1.2.2",
"@octokit/rest": "^19.0.7",
"@picgo/i18n": "^1.0.0",
"@picgo/store": "^2.0.4",
+ "@types/marked": "^4.0.8",
"@types/mime-types": "^2.1.1",
+ "@videojs-player/vue": "^1.0.0",
"ali-oss": "^6.17.1",
"aws-sdk": "^2.1317.0",
"axios": "^1.3.2",
@@ -41,10 +44,12 @@
"form-data": "^4.0.0",
"fs-extra": "^11.1.0",
"got": "^12.5.3",
+ "highlight.js": "^11.7.0",
"hpagent": "^1.2.0",
"keycode": "^2.2.0",
"lodash-id": "^0.14.0",
"lowdb": "^1.0.0",
+ "marked": "^4.2.12",
"mime-types": "^2.1.35",
"mitt": "^3.0.0",
"piclist": "^0.0.8",
@@ -55,10 +60,12 @@
"shell-path": "2.1.0",
"upyun": "^3.4.6",
"uuid": "^9.0.0",
+ "video.js": "^8.0.4",
"vue": "^3.2.47",
"vue-router": "^4.1.6",
"vue3-lazyload": "^0.3.6",
"vue3-photo-preview": "^0.2.9",
+ "webdav": "^5.0.0-r3",
"write-file-atomic": "^4.0.1"
},
"devDependencies": {
diff --git a/scripts/cos-link.js b/scripts/link.js
similarity index 100%
rename from scripts/cos-link.js
rename to scripts/link.js
diff --git a/src/main.ts b/src/main.ts
index 337fd94..251ba8c 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -18,6 +18,11 @@ import vue3PhotoPreview from 'vue3-photo-preview'
import 'vue3-photo-preview/dist/index.css'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
+import 'highlight.js/styles/atom-one-dark.css'
+import hljsVuePlugin from '@highlightjs/vue-plugin'
+import hljsCommon from 'highlight.js/lib/common'
+import VueVideoPlayer from '@videojs-player/vue'
+import 'video.js/dist/video-js.css'
webFrame.setVisualZoomLevelLimits(1, 1)
@@ -60,6 +65,9 @@ app.use(store)
app.use(vue3PhotoPreview)
app.use(pinia)
app.use(ContextMenu)
+console.log(hljsCommon.highlightAuto('
Highlight.js has been registered successfully!
').value)
+app.use(hljsVuePlugin)
+app.use(VueVideoPlayer)
app.mount('#app')
initTalkingData()
diff --git a/src/main/manage/apis/api.ts b/src/main/manage/apis/api.ts
index b183431..3f611fe 100644
--- a/src/main/manage/apis/api.ts
+++ b/src/main/manage/apis/api.ts
@@ -6,6 +6,7 @@ import SmmsApi from './smms'
import GithubApi from './github'
import ImgurApi from './imgur'
import S3plistApi from './s3plist'
+import WebdavplistApi from './webdavplist'
export default {
TcyunApi,
@@ -15,5 +16,6 @@ export default {
SmmsApi,
GithubApi,
ImgurApi,
- S3plistApi
+ S3plistApi,
+ WebdavplistApi
}
diff --git a/src/main/manage/apis/s3plist.ts b/src/main/manage/apis/s3plist.ts
index c5d8e03..f91b6e5 100644
--- a/src/main/manage/apis/s3plist.ts
+++ b/src/main/manage/apis/s3plist.ts
@@ -18,7 +18,7 @@ import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
import https from 'https'
import http from 'http'
import { ManageLogger } from '../utils/logger'
-import { formatError, getAgent, getFileMimeType, gotDownload } from '../utils/common'
+import { formatEndpoint, formatError, getAgent, getFileMimeType, gotDownload } from '../utils/common'
import { isImage } from '@/manage/utils/common'
import { HttpsProxyAgent, HttpProxyAgent } from 'hpagent'
import windowManager from 'apis/app/window/windowManager'
@@ -64,7 +64,7 @@ class S3plistApi {
accessKeyId,
secretAccessKey
},
- endpoint: endpoint ? this.formatEndpoint(endpoint, sslEnabled) : undefined,
+ endpoint: endpoint ? formatEndpoint(endpoint, sslEnabled) : undefined,
sslEnabled,
s3ForcePathStyle,
httpOptions: {
@@ -75,9 +75,6 @@ class S3plistApi {
this.agent = this.setAgent(proxy, sslEnabled)
}
- formatEndpoint = (endpoint: string, sslEnabled: boolean): string =>
- !/^https?:\/\//.test(endpoint) ? `${sslEnabled ? 'https' : 'http'}://${endpoint}` : endpoint
-
setAgent (proxy: string | undefined, sslEnabled: boolean) : HttpProxyAgent | HttpsProxyAgent | undefined {
if (sslEnabled) {
const agent = getAgent(proxy, true).https
diff --git a/src/main/manage/apis/webdavplist.ts b/src/main/manage/apis/webdavplist.ts
new file mode 100644
index 0000000..58a2757
--- /dev/null
+++ b/src/main/manage/apis/webdavplist.ts
@@ -0,0 +1,130 @@
+import ManageLogger from '../utils/logger'
+import { createClient, WebDAVClient, FileStat } from 'webdav'
+import { formatError, formatEndpoint, getInnerAgent } from '../utils/common'
+import { isImage } from '@/manage/utils/common'
+import http from 'http'
+import https from 'https'
+import windowManager from 'apis/app/window/windowManager'
+import { IWindowList } from '#/types/enum'
+import { ipcMain, IpcMainEvent } from 'electron'
+
+class WebdavplistApi {
+ endpoint: string
+ username: string
+ password: string
+ sslEnabled: boolean
+ proxy: string | undefined
+ logger: ManageLogger
+ agent: https.Agent | http.Agent
+ ctx: WebDAVClient
+
+ constructor (endpoint: string, username: string, password: string, sslEnabled: boolean, proxy: string | undefined, logger: ManageLogger) {
+ this.endpoint = formatEndpoint(endpoint, sslEnabled)
+ this.username = username
+ this.password = password
+ this.sslEnabled = sslEnabled
+ this.proxy = proxy
+ this.logger = logger
+ this.agent = getInnerAgent(proxy, sslEnabled).agent
+ this.ctx = createClient(
+ this.endpoint,
+ {
+ username: this.username,
+ password: this.password,
+ maxBodyLength: 4 * 1024 * 1024 * 1024,
+ maxContentLength: 4 * 1024 * 1024 * 1024,
+ httpsAgent: sslEnabled ? this.agent : undefined,
+ httpAgent: !sslEnabled ? this.agent : undefined
+ }
+ )
+ }
+
+ logParam = (error:any, method: string) =>
+ this.logger.error(formatError(error, { class: 'WebdavplistApi', method }))
+
+ formatFolder (item: FileStat, urlPrefix: string) {
+ return {
+ ...item,
+ key: item.filename.replace(/^\/+/, ''),
+ fileName: item.basename,
+ fileSize: 0,
+ Key: item.filename.replace(/^\/+/, ''),
+ formatedTime: '',
+ isDir: true,
+ checked: false,
+ isImage: false,
+ match: false,
+ url: `${urlPrefix}${item.filename}`
+ }
+ }
+
+ formatFile (item: FileStat, urlPrefix: string) {
+ return {
+ ...item,
+ key: item.filename.replace(/^\/+/, ''),
+ fileName: item.basename,
+ fileSize: item.size,
+ Key: item.filename.replace(/^\/+/, ''),
+ formatedTime: new Date(item.lastmod).toLocaleString(),
+ isDir: false,
+ checked: false,
+ match: false,
+ isImage: isImage(item.basename),
+ url: `${urlPrefix}${item.filename}`
+ }
+ }
+
+ isRequestSuccess = (code: number) => code >= 200 && code < 300
+
+ async getBucketListBackstage (configMap: IStringKeyMap): Promise {
+ const window = windowManager.get(IWindowList.SETTING_WINDOW)!
+ const { prefix, customUrl, cancelToken } = configMap
+ const urlPrefix = customUrl || this.endpoint
+ const cancelTask = [false]
+ ipcMain.on('cancelLoadingFileList', (_evt: IpcMainEvent, token: string) => {
+ if (token === cancelToken) {
+ cancelTask[0] = true
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+ })
+ let res = {} as any
+ const result = {
+ fullList: [],
+ success: false,
+ finished: false
+ }
+ try {
+ res = await this.ctx.getDirectoryContents(prefix, {
+ deep: false,
+ details: true
+ })
+ if (this.isRequestSuccess(res.status)) {
+ if (res.data && res.data.length) {
+ res.data.forEach((item: FileStat) => {
+ if (item.type === 'directory') {
+ result.fullList.push(this.formatFolder(item, urlPrefix))
+ } else {
+ result.fullList.push(this.formatFile(item, urlPrefix))
+ }
+ })
+ }
+ } else {
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ return
+ }
+ } catch (error) {
+ this.logParam(error, 'getBucketListBackstage')
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+ result.success = true
+ result.finished = true
+ window.webContents.send('refreshFileTransferList', result)
+ ipcMain.removeAllListeners('cancelLoadingFileList')
+ }
+}
+
+export default WebdavplistApi
diff --git a/src/main/manage/manageApi.ts b/src/main/manage/manageApi.ts
index 5458c23..6771915 100644
--- a/src/main/manage/manageApi.ts
+++ b/src/main/manage/manageApi.ts
@@ -69,6 +69,8 @@ export class ManageApi extends EventEmitter implements ManageApiType {
return new API.ImgurApi(this.currentPicBedConfig.imgurUserName, this.currentPicBedConfig.accessToken, this.currentPicBedConfig.proxy, this.logger)
case 's3plist':
return new API.S3plistApi(this.currentPicBedConfig.accessKeyId, this.currentPicBedConfig.secretAccessKey, this.currentPicBedConfig.endpoint, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.s3ForcePathStyle, this.currentPicBedConfig.proxy, this.logger)
+ case 'webdavplist':
+ return new API.WebdavplistApi(this.currentPicBedConfig.endpoint, this.currentPicBedConfig.username, this.currentPicBedConfig.password, this.currentPicBedConfig.sslEnabled, this.currentPicBedConfig.proxy, this.logger)
default:
return {} as any
}
@@ -172,6 +174,12 @@ export class ManageApi extends EventEmitter implements ManageApiType {
Location: 'smms',
CreationDate: new Date().toISOString()
}]
+ case 'webdavplist':
+ return [{
+ Name: 'webdav',
+ Location: 'webdav',
+ CreationDate: new Date().toISOString()
+ }]
default:
console.log(param)
return []
@@ -309,6 +317,7 @@ export class ManageApi extends EventEmitter implements ManageApiType {
case 'github':
case 'imgur':
case 's3plist':
+ case 'webdavplist':
try {
client = this.createClient() as any
return await client.getBucketListBackstage(param!)
diff --git a/src/main/manage/utils/common.ts b/src/main/manage/utils/common.ts
index f04f1c8..faa344e 100644
--- a/src/main/manage/utils/common.ts
+++ b/src/main/manage/utils/common.ts
@@ -14,8 +14,10 @@ import UpDownTaskQueue,
downloadTaskSpecialStatus
} from '../datastore/upDownTaskQueue'
import { ManageLogger } from '../utils/logger'
-import { formatHttpProxy } from '@/manage/utils/common'
+import { formatHttpProxy, IHTTPProxy } from '@/manage/utils/common'
import { HttpsProxyAgent, HttpProxyAgent } from 'hpagent'
+import http from 'http'
+import https from 'https'
export const getFSFile = async (
filePath: string,
@@ -244,6 +246,47 @@ export const getAgent = (proxy:any, https: boolean = true) => {
}
}
+export const getInnerAgent = (proxy: any, sslEnabled: boolean = true) => {
+ const formatProxy = formatHttpProxy(proxy, 'object') as IHTTPProxy
+ if (sslEnabled) {
+ return formatProxy
+ ? {
+ agent: new https.Agent({
+ keepAlive: true,
+ keepAliveMsecs: 1000,
+ rejectUnauthorized: false,
+ scheduling: 'lifo' as 'lifo' | 'fifo' | undefined,
+ host: formatProxy.host,
+ port: formatProxy.port
+ })
+ }
+ : {
+ agent: new https.Agent({
+ rejectUnauthorized: false,
+ keepAlive: true
+ })
+ }
+ } else {
+ return formatProxy
+ ? {
+ agent: new http.Agent({
+ keepAlive: true,
+ keepAliveMsecs: 1000,
+ scheduling: 'lifo' as 'lifo' | 'fifo' | undefined,
+ host: formatProxy.host,
+ port: formatProxy.port
+ })
+ }
+ : {
+ agent: new http.Agent({
+ keepAlive: true,
+ keepAliveMsecs: 1000,
+ scheduling: 'lifo' as 'lifo' | 'fifo' | undefined
+ })
+ }
+ }
+}
+
export function getOptions (
method?: string,
headers?: IStringKeyMap,
@@ -270,3 +313,10 @@ export function getOptions (
})
return options
}
+
+export const formatEndpoint = (endpoint: string, sslEnabled: boolean): string =>
+ !/^https?:\/\//.test(endpoint)
+ ? `${sslEnabled ? 'https' : 'http'}://${endpoint}`
+ : sslEnabled
+ ? endpoint.replace('http://', 'https://')
+ : endpoint.replace('https://', 'http://')
diff --git a/src/renderer/manage/pages/assets/webdavplist.png b/src/renderer/manage/pages/assets/webdavplist.png
new file mode 100644
index 0000000..1c3b190
Binary files /dev/null and b/src/renderer/manage/pages/assets/webdavplist.png differ
diff --git a/src/renderer/manage/pages/bucketPage.vue b/src/renderer/manage/pages/bucketPage.vue
index d4275cd..fe5bf8a 100644
--- a/src/renderer/manage/pages/bucketPage.vue
+++ b/src/renderer/manage/pages/bucketPage.vue
@@ -1,4 +1,4 @@
-/*
+ea/*
*UI布局和部分样式代码参考了https://github.com/willnewii/qiniuClient
*感谢作者@willnewii
*/
@@ -29,7 +29,7 @@
/>
+
+
+ {isShowMarkDownDialog = false}"
+ />
+
+
+
+ {isShowTextFileDialog = false}"
+ />
+
+
+
+ {isShowVideoFileDialog = false}"
+ />
+