feat: 路径收藏添加发送到终端按钮
This commit is contained in:
@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import { useFavoritePathsStore, type FavoritePathItem } from '../stores/favoritePaths.store';
|
import { useFavoritePathsStore, type FavoritePathItem } from '../stores/favoritePaths.store';
|
||||||
import { useSessionStore } from '../stores/session.store';
|
import { useSessionStore } from '../stores/session.store';
|
||||||
import AddEditFavoritePathForm from './AddEditFavoritePathForm.vue';
|
import AddEditFavoritePathForm from './AddEditFavoritePathForm.vue';
|
||||||
|
import { useWorkspaceEventEmitter } from '../composables/workspaceEvents';
|
||||||
|
|
||||||
const PADDING = 8; // px
|
const PADDING = 8; // px
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ const emit = defineEmits(['close', 'navigateToPath']);
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const favoritePathsStore = useFavoritePathsStore();
|
const favoritePathsStore = useFavoritePathsStore();
|
||||||
const sessionStore = useSessionStore();
|
const sessionStore = useSessionStore();
|
||||||
|
const emitWorkspaceEvent = useWorkspaceEventEmitter();
|
||||||
|
|
||||||
const searchTerm = ref('');
|
const searchTerm = ref('');
|
||||||
const showAddEditModal = ref(false);
|
const showAddEditModal = ref(false);
|
||||||
@@ -89,6 +91,11 @@ const handleDelete = async (pathItem: FavoritePathItem) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSendToTerminal = (pathItem: FavoritePathItem) => {
|
||||||
|
emitWorkspaceEvent('favoritePath:sendToActiveTerminal', { path: pathItem.path });
|
||||||
|
closeModal(); // Optionally close modal after sending
|
||||||
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
emit('close');
|
emit('close');
|
||||||
};
|
};
|
||||||
@@ -255,6 +262,12 @@ onBeforeUnmount(() => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-shrink-0 flex items-center gap-1 opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity duration-150">
|
<div class="flex-shrink-0 flex items-center gap-1 opacity-0 group-hover:opacity-100 focus-within:opacity-100 transition-opacity duration-150">
|
||||||
|
<button
|
||||||
|
@click.stop="handleSendToTerminal(favPath)"
|
||||||
|
class="p-1.5 rounded text-text-secondary hover:text-primary hover:bg-black/10 dark:hover:bg-white/10 transition-colors"
|
||||||
|
:title="t('favoritePaths.sendToTerminal', 'Send to Terminal')">
|
||||||
|
<i class="fas fa-terminal text-xs"></i>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
@click.stop="openEditModal(favPath)"
|
@click.stop="openEditModal(favPath)"
|
||||||
class="p-1.5 rounded text-text-secondary hover:text-primary hover:bg-black/10 dark:hover:bg-white/10 transition-colors"
|
class="p-1.5 rounded text-text-secondary hover:text-primary hover:bg-black/10 dark:hover:bg-white/10 transition-colors"
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ import { Terminal, ITerminalAddon, IDisposable } from 'xterm';
|
|||||||
import { useDeviceDetection } from '../composables/useDeviceDetection';
|
import { useDeviceDetection } from '../composables/useDeviceDetection';
|
||||||
import { useAppearanceStore } from '../stores/appearance.store';
|
import { useAppearanceStore } from '../stores/appearance.store';
|
||||||
import { useSettingsStore } from '../stores/settings.store';
|
import { useSettingsStore } from '../stores/settings.store';
|
||||||
|
import { useSessionStore } from '../stores/session.store';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { FitAddon } from '@xterm/addon-fit';
|
import { FitAddon } from '@xterm/addon-fit';
|
||||||
import { WebLinksAddon } from 'xterm-addon-web-links';
|
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||||
import { SearchAddon, type ISearchOptions } from '@xterm/addon-search';
|
import { SearchAddon, type ISearchOptions } from '@xterm/addon-search';
|
||||||
import 'xterm/css/xterm.css';
|
import 'xterm/css/xterm.css';
|
||||||
import { useWorkspaceEventEmitter } from '../composables/workspaceEvents';
|
import { useWorkspaceEventEmitter, useWorkspaceEventSubscriber, useWorkspaceEventOff } from '../composables/workspaceEvents'; // +++ Import subscriber and off
|
||||||
|
|
||||||
|
|
||||||
// 定义 props 和 emits
|
// 定义 props 和 emits
|
||||||
@@ -23,6 +24,8 @@ const props = defineProps<{
|
|||||||
|
|
||||||
|
|
||||||
const emitWorkspaceEvent = useWorkspaceEventEmitter(); // +++ 获取事件发射器 +++
|
const emitWorkspaceEvent = useWorkspaceEventEmitter(); // +++ 获取事件发射器 +++
|
||||||
|
const subscribeToWorkspaceEvent = useWorkspaceEventSubscriber(); // +++ 获取事件订阅器 +++
|
||||||
|
const unsubscribeFromWorkspaceEvent = useWorkspaceEventOff(); // +++ 获取事件取消订阅器 +++
|
||||||
|
|
||||||
const terminalRef = ref<HTMLElement | null>(null); // xterm 挂载点的引用 (内部容器)
|
const terminalRef = ref<HTMLElement | null>(null); // xterm 挂载点的引用 (内部容器)
|
||||||
const terminalOuterWrapperRef = ref<HTMLElement | null>(null); // 最外层容器的引用,用于背景图
|
const terminalOuterWrapperRef = ref<HTMLElement | null>(null); // 最外层容器的引用,用于背景图
|
||||||
@@ -65,6 +68,7 @@ const isTerminalDomReady = ref(false);
|
|||||||
|
|
||||||
// --- Settings Store ---
|
// --- Settings Store ---
|
||||||
const settingsStore = useSettingsStore(); // +++ 实例化设置 store +++
|
const settingsStore = useSettingsStore(); // +++ 实例化设置 store +++
|
||||||
|
const sessionStore = useSessionStore(); // +++ 实例化会话 store +++
|
||||||
const {
|
const {
|
||||||
autoCopyOnSelectBoolean,
|
autoCopyOnSelectBoolean,
|
||||||
terminalScrollbackLimitNumber,
|
terminalScrollbackLimitNumber,
|
||||||
@@ -569,6 +573,17 @@ onMounted(() => {
|
|||||||
terminalRef.value.addEventListener('touchend', handleTouchEnd, { passive: false });
|
terminalRef.value.addEventListener('touchend', handleTouchEnd, { passive: false });
|
||||||
terminalRef.value.addEventListener('touchcancel', handleTouchEnd, { passive: false }); // Also handle cancel
|
terminalRef.value.addEventListener('touchcancel', handleTouchEnd, { passive: false }); // Also handle cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen for favorite path to send to terminal
|
||||||
|
subscribeToWorkspaceEvent('favoritePath:sendToActiveTerminal', ({ path }) => {
|
||||||
|
if (terminal && props.isActive && sessionStore.activeSessionId === props.sessionId) {
|
||||||
|
// Ensure path is quoted to handle spaces or special characters
|
||||||
|
const command = `cd "${path.replace(/"/g, '\\"')}"\n`; // Escape existing quotes in path
|
||||||
|
emitWorkspaceEvent('terminal:input', { sessionId: props.sessionId, data: command });
|
||||||
|
terminal.focus(); // Focus terminal after sending command
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -612,9 +627,9 @@ onBeforeUnmount(() => {
|
|||||||
terminalRef.value.removeEventListener('touchcancel', handleTouchEnd);
|
terminalRef.value.removeEventListener('touchcancel', handleTouchEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
// terminalRef 是内部容器,不需要特别处理
|
unsubscribeFromWorkspaceEvent('favoritePath:sendToActiveTerminal');
|
||||||
// if (terminalRef.value) {
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
// 暴露 write 方法给父组件 (可选)
|
// 暴露 write 方法给父组件 (可选)
|
||||||
const write = (data: string | Uint8Array) => {
|
const write = (data: string | Uint8Array) => {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export type WorkspaceEventPayloads = {
|
|||||||
|
|
||||||
// Suspended SSH Session Events
|
// Suspended SSH Session Events
|
||||||
'suspendedSession:actionCompleted': void; // Emitted when a resume/remove action is completed
|
'suspendedSession:actionCompleted': void; // Emitted when a resume/remove action is completed
|
||||||
|
'favoritePath:sendToActiveTerminal': { path: string }; // Event to send a path to the active terminal
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建 mitt 事件发射器实例
|
// 创建 mitt 事件发射器实例
|
||||||
|
|||||||
Reference in New Issue
Block a user