diff --git a/package.json b/package.json index 773f364..4b34f23 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "aes-js": "^3.1.2", "axios": "^1.7.3", "codemirror": "^6.0.1", + "copy-to-clipboard": "^3.3.3", "lodash": "^4.17.21", "markdown-it": "^14.1.0", "naive-ui": "^2.39.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c8726e..1e8a8c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ importers: codemirror: specifier: ^6.0.1 version: 6.0.1(@lezer/common@1.2.1) + copy-to-clipboard: + specifier: ^3.3.3 + version: 3.3.3 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -672,6 +675,9 @@ packages: console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + core-js@3.38.1: resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} @@ -1103,6 +1109,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -1877,6 +1886,10 @@ snapshots: console-control-strings@1.1.0: optional: true + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + core-js@3.38.1: {} crelt@1.0.6: {} @@ -2371,6 +2384,8 @@ snapshots: dependencies: is-number: 7.0.0 + toggle-selection@1.0.6: {} + tr46@0.0.3: optional: true diff --git a/src/App.vue b/src/App.vue index bce09de..f2ea87a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,5 +1,5 @@ @@ -369,6 +349,7 @@ $tag-width: 170px; .view { .view-controller { .tabs-container { + .tab-move, .tab-enter-active { transition: all 0.2s ease; diff --git a/src/class/PluginCenter.ts b/src/class/PluginCenter.ts index 04145ee..b8f05d0 100644 --- a/src/class/PluginCenter.ts +++ b/src/class/PluginCenter.ts @@ -1,124 +1,119 @@ -import useVirtualPages from '@/hooks/useVirtualPages' -import VirtualDirectory, { VirtualFile } from './VirtualDirectory' -import { ShallowRef, shallowRef } from 'vue' +import useVirtualPages from "@/hooks/useVirtualPages"; +import VirtualDirectory, { VirtualFile } from "./VirtualDirectory"; +import { ShallowRef, shallowRef } from "vue"; +import { alertTipType, usePluginHooksType } from "@/types/type"; export enum PluginGroup { - default = 'default', - mainMenu = 'mainMenu' + default = "default", + mainMenu = "mainMenu" } export interface registerComponentOption { - key: string - name: string - icon?: any - getComponent: () => Promise - group?: PluginGroup[] - filter: (VDirectories: VirtualDirectory[], VFiles: VirtualFile[]) => boolean - disable?: boolean - exitConfirm?: boolean + key: string; + name: string; + icon?: any; + getComponent: () => Promise; + group?: PluginGroup[]; + filter: (VDirectories: VirtualDirectory[], VFiles: VirtualFile[]) => boolean; + disable?: boolean; + exitConfirm?: boolean; } -type Plugin = { - // 用于显示的名字 - name: string - // 代表插件的icon - icon?: ShallowRef - // 是否禁用插件 - disable: boolean - // 插件所属的group,用于区分插件使用的场合 - group: PluginGroup[] - // 判断在未禁用时是否可以使用这个组件 - // 使用组件即调用下面的func - filter: (VDirectories: VirtualDirectory[], VFiles: VirtualFile[]) => boolean - func: ( - VDirectories: VirtualDirectory[], - VFiles: VirtualFile[], - ...args: any[] - ) => void -} +export type Plugin = { + // 用于显示的名字 + name: string; + // 代表插件的icon + icon?: ShallowRef; + // 是否禁用插件 + disable: boolean; + // 插件所属的group,用于区分插件使用的场合 + group: PluginGroup[]; + // 判断在未禁用时是否可以使用这个组件 + // 使用组件即调用下面的func + filter: (VDirectories: VirtualDirectory[], VFiles: VirtualFile[]) => boolean; + func: ( + VDirectories: VirtualDirectory[], + VFiles: VirtualFile[], + hooks: usePluginHooksType, + ...args: any[] + ) => void; +}; export default class PluginCenter { - constructor() { - this.pluginsMap = {} - } - pluginsMap: { - [key: string]: Plugin - } + constructor(globalAlertTip: alertTipType) { + this.pluginsMap = {}; + this.globalAlertTip = globalAlertTip; + } + pluginsMap: { + [key: string]: Plugin; + }; - register(key: string, plugin: Plugin) { - this.pluginsMap[key] = plugin - } + globalAlertTip: alertTipType; - /** - * 便捷地注册一个组件为插件,触发时直接用新页面打开 - */ - registerComponent(option: registerComponentOption): void { - const { - key, - name, - getComponent, - icon, - filter, - group = [PluginGroup.default], - disable = false, - exitConfirm = false - } = option - let _component: any - const views = useVirtualPages() - this.register(key, { - name, - filter, - icon: shallowRef(icon), - group, - disable, - func: async ( - VDirectories: VirtualDirectory[], - VFiles: VirtualFile[] - ) => { - if (filter(VDirectories, VFiles) === false) { - return - } - if (_component === undefined) _component = await getComponent() - views.push(_component, VDirectories, VFiles, { - VirtualPageOption: { - exitConfirm - } - }) - } - }) - } + register(key: string, plugin: Plugin) { + this.pluginsMap[key] = plugin; + } - getUsablePlugin( - VDirectories: VirtualDirectory[], - VFiles: VirtualFile[], - option?: { - group?: PluginGroup[] - } - ) { - const { group = [PluginGroup.default] } = option ?? {} - const usableList = [] - for (let key of Object.keys(this.pluginsMap)) { - const _plugin = this.pluginsMap[key] - if ( - !_plugin.disable && - _plugin.group.filter((v) => group.includes(v)).length > 0 && - _plugin.filter(VDirectories, VFiles) - ) { - usableList.push(key) - } - } - return usableList.map((key) => ({ - key, - ...this.pluginsMap[key] - })) - } + /** + * 便捷地注册一个组件为插件,触发时直接用新页面打开 + */ + registerComponent(option: registerComponentOption): void { + const { key, name, getComponent, icon, filter, group = [PluginGroup.default], disable = false, exitConfirm = false } = option; + let _component: any; + const views = useVirtualPages(); + this.register(key, { + name, + filter, + icon: shallowRef(icon), + group, + disable, + func: async (VDirectories: VirtualDirectory[], VFiles: VirtualFile[]) => { + if (filter(VDirectories, VFiles) === false) { + return; + } - usePlugin( - key: string, - VDirectories?: VirtualDirectory[], - VFiles?: VirtualFile[], - ...args: any[] - ) { - this.pluginsMap[key].func(VDirectories ?? [], VFiles ?? [], ...args) - } + if (_component === undefined) { + const setAlertTip = key !== "miaoDirectory" && this.globalAlertTip(`加载 ${name} 插件中`); + _component = await getComponent(); + setAlertTip && + setAlertTip(`加载 ${name} 插件完成`, { + type: "success", + timeout: 2000 + }); + } + views.push(_component, VDirectories, VFiles, { + VirtualPageOption: { + exitConfirm + } + }); + } + }); + } + + getUsablePlugin( + VDirectories: VirtualDirectory[], + VFiles: VirtualFile[], + option?: { + group?: PluginGroup[]; + } + ) { + const { group = [PluginGroup.default] } = option ?? {}; + const usableList = []; + for (let key of Object.keys(this.pluginsMap)) { + const _plugin = this.pluginsMap[key]; + if (!_plugin.disable && _plugin.group.filter(v => group.includes(v)).length > 0 && _plugin.filter(VDirectories, VFiles)) { + usableList.push(key); + } + } + return usableList.map(key => ({ + key, + ...this.pluginsMap[key] + })); + } + + usePlugin(key: string, VDirectories?: VirtualDirectory[], VFiles?: VirtualFile[], ...args: any[]) { + this.pluginsMap[key].func(VDirectories ?? [], VFiles ?? [], { + globalAlertTip: this.globalAlertTip + }, ...args); + } } diff --git a/src/class/VirtualDirectory.ts b/src/class/VirtualDirectory.ts index 58745dd..890a730 100644 --- a/src/class/VirtualDirectory.ts +++ b/src/class/VirtualDirectory.ts @@ -6,6 +6,21 @@ import type { stats, file, directory } from "@/types/type.ts"; const miaoFetchApi = useMiaoFetchApi(); +export interface Tree { + name: string; + type: 'directory'; + path: string; + stats: stats; + files?: { + name: string; + type: 'file'; + size: number; + stats: stats; + url: string; + }[]; + directories?: Tree[]; +} + export class VirtualFile { constructor(info: file, parent: VirtualDirectory) { this.name = info.name; @@ -26,7 +41,7 @@ export class VirtualFile { } get url() { - return `${config.api.get}${this.parent.path}${this.name}` + return `${config.api.get}${this.parent.path}${this.name}`; } } @@ -96,6 +111,24 @@ class VirtualDirectory { return this.parent.getParent(layer - 1); } + get tree(): Tree { + return { + name: this.name, + type: this.type, + path: this.path, + stats: this.stats, + files: + this.files?.map(v => ({ + name: v.name, + type: v.type, + size: v.size, + stats: v.stats, + url: v.url + })) ?? [], + directories: this.directories?.map(v => v.tree) ?? [] + }; + } + updateContent(content: (file | directory)[]) { this.files = this.files ?? []; this.directories = this.directories ?? []; diff --git a/src/components/miaoMessageProvider.vue b/src/components/miaoAlertTipProvider.vue similarity index 92% rename from src/components/miaoMessageProvider.vue rename to src/components/miaoAlertTipProvider.vue index 5210b2e..f5cba1e 100644 --- a/src/components/miaoMessageProvider.vue +++ b/src/components/miaoAlertTipProvider.vue @@ -18,12 +18,7 @@ import generateId from '@/hooks/generateId'; import { NIcon } from 'naive-ui'; import { computed, onMounted, ref, shallowReactive } from 'vue'; import { InformationCircle, CheckmarkCircle, CloseCircle } from '@vicons/ionicons5' - -interface MessageOptions { - type?: 'info' | 'success' | 'error' - timeout?: number - id?: number -} +import { AlertTipMessageOptions } from '@/types/type'; interface Message { icon: any, @@ -48,7 +43,7 @@ const messageList = computed(() => { ...messageMap[v], })) }) -const message = (content: string, options?: MessageOptions) => { +const alertTip = (content: string, options?: AlertTipMessageOptions) => { let timer: any const { timeout = 0, type = 'info', id = generateId() } = options ?? {} if (timeout > 0) { @@ -63,10 +58,10 @@ const message = (content: string, options?: MessageOptions) => { content, timer } - return (newContent: string, newOptions?: MessageOptions) => { + return (newContent: string, newOptions?: AlertTipMessageOptions) => { newOptions = newOptions ?? {} newOptions.id = id - message(newContent, newOptions) + alertTip(newContent, newOptions) } } @@ -86,7 +81,7 @@ onMounted(async () => { }) -defineExpose({ message }); +defineExpose({ alertTip });