add key short in edit inline

This commit is contained in:
duanfuxiang 2025-02-18 12:31:04 +08:00
parent d15681b0d5
commit 76a5799067
3 changed files with 82 additions and 15 deletions

View File

@ -1,7 +1,7 @@
import { Change, diffLines } from 'diff' import { Change, diffLines } from 'diff'
import { CheckIcon, X } from 'lucide-react' import { CheckIcon, X } from 'lucide-react'
import { getIcon } from 'obsidian' import { Platform, getIcon } from 'obsidian'
import { useState } from 'react' import { useEffect, useState } from 'react'
import { ApplyViewState } from '../../ApplyView' import { ApplyViewState } from '../../ApplyView'
import { useApp } from '../../contexts/AppContext' import { useApp } from '../../contexts/AppContext'
@ -17,6 +17,14 @@ export default function ApplyViewRoot({
const rejectIcon = getIcon('x') const rejectIcon = getIcon('x')
const excludeIcon = getIcon('x') const excludeIcon = getIcon('x')
const getShortcutText = (shortcut: 'accept' | 'reject') => {
const isMac = Platform.isMacOS
if (shortcut === 'accept') {
return isMac ? '(⌘⏎)' : '(Ctrl+⏎)'
}
return isMac ? '(⌘⌫)' : '(Ctrl+⌫)'
}
const app = useApp() const app = useApp()
const [diff, setDiff] = useState<Change[]>( const [diff, setDiff] = useState<Change[]>(
@ -64,6 +72,30 @@ export default function ApplyViewRoot({
}) })
} }
const handleKeyDown = (event: KeyboardEvent) => {
const modifierKey = Platform.isMacOS ? event.metaKey : event.ctrlKey;
if (modifierKey) {
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
handleAccept();
} else if (event.key === 'Backspace') {
event.preventDefault();
event.stopPropagation();
handleReject();
}
}
}
// 在组件挂载时添加事件监听器,在卸载时移除
useEffect(() => {
const handler = (e: KeyboardEvent) => handleKeyDown(e);
window.addEventListener('keydown', handler, true);
return () => {
window.removeEventListener('keydown', handler, true);
}
}, [handleAccept, handleReject]) // 添加handleAccept和handleReject作为依赖项
return ( return (
<div id="infio-apply-view"> <div id="infio-apply-view">
<div className="view-header"> <div className="view-header">
@ -81,7 +113,7 @@ export default function ApplyViewRoot({
onClick={handleAccept} onClick={handleAccept}
> >
{acceptIcon && <CheckIcon size={14} />} {acceptIcon && <CheckIcon size={14} />}
Accept Accept {getShortcutText('accept')}
</button> </button>
<button <button
className="clickable-icon view-action infio-reject-button" className="clickable-icon view-action infio-reject-button"
@ -89,7 +121,7 @@ export default function ApplyViewRoot({
onClick={handleReject} onClick={handleReject}
> >
{rejectIcon && <X size={14} />} {rejectIcon && <X size={14} />}
Reject Reject {getShortcutText('reject')}
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import { MarkdownView, Plugin } from 'obsidian'; import { MarkdownView, Plugin, Platform } from 'obsidian';
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState } from 'react';
import { CornerDownLeft } from 'lucide-react';
import { APPLY_VIEW_TYPE } from '../../constants'; import { APPLY_VIEW_TYPE } from '../../constants';
import LLMManager from '../../core/llm/manager'; import LLMManager from '../../core/llm/manager';
import { InfioSettings } from '../../types/settings'; import { InfioSettings } from '../../types/settings';
@ -20,24 +20,41 @@ type InlineEditProps = {
type InputAreaProps = { type InputAreaProps = {
value: string; value: string;
onChange: (value: string) => void; onChange: (value: string) => void;
handleSubmit: () => void;
handleClose: () => void;
} }
const InputArea: React.FC<InputAreaProps> = ({ value, onChange }) => { const InputArea: React.FC<InputAreaProps> = ({ value, onChange, handleSubmit, handleClose }) => {
const textareaRef = useRef<HTMLTextAreaElement>(null); const textareaRef = useRef<HTMLTextAreaElement>(null);
useEffect(() => { useEffect(() => {
// 组件挂载后自动聚焦到 textarea
textareaRef.current?.focus(); textareaRef.current?.focus();
}, []); }, []);
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter') {
if (e.shiftKey) {
// Shift + Enter: 允许换行,使用默认行为
return;
}
// 普通 Enter: 阻止默认行为并触发提交
e.preventDefault();
handleSubmit();
} else if (e.key === 'Escape') {
// 当按下 Esc 键时关闭编辑器
handleClose();
}
};
return ( return (
<div className="infio-ai-block-input-wrapper"> <div className="infio-ai-block-input-wrapper">
<textarea <textarea
ref={textareaRef} ref={textareaRef}
className="infio-ai-block-content" className="infio-ai-block-content"
placeholder="Enter instruction" placeholder="Input instruction, Enter to submit, Esc to close"
value={value} value={value}
onChange={(e) => onChange(e.target.value)} onChange={(e) => onChange(e.target.value)}
onKeyDown={handleKeyDown}
/> />
</div> </div>
); );
@ -91,7 +108,12 @@ const ControlArea: React.FC<ControlAreaProps> = ({
onClick={onSubmit} onClick={onSubmit}
disabled={isSubmitting} disabled={isSubmitting}
> >
{isSubmitting ? "Submitting..." : "Submit"} {isSubmitting ? "submitting..." : (
<>
submit
<CornerDownLeft size={11} className="infio-ai-block-submit-icon" />
</>
)}
</button> </button>
</div>); </div>);
}; };
@ -251,9 +273,8 @@ export const InlineEdit: React.FC<InlineEditProps> = ({
return ( return (
<div className="infio-ai-block-container" <div className="infio-ai-block-container"
id="infio-ai-block-container" id="infio-ai-block-container"
style={{ backgroundColor: 'var(--background-secondary)' }}
> >
<InputArea value={instruction} onChange={setInstruction} /> <InputArea value={instruction} onChange={setInstruction} handleSubmit={handleSubmit} handleClose={handleClose} />
<button className="infio-ai-block-close-button" onClick={handleClose}> <button className="infio-ai-block-close-button" onClick={handleClose}>
<svg <svg
width="14" width="14"

View File

@ -1431,7 +1431,7 @@ input[type='text'].infio-chat-list-dropdown-item-title-input {
} }
.infio-ai-block-container { .infio-ai-block-container {
background: var(--background-secondary-alt); background: var(--background-secondary);
border-radius: 4px; border-radius: 4px;
padding: 0px; padding: 0px;
border: 1px solid var(--background-modifier-border); border: 1px solid var(--background-modifier-border);
@ -1443,11 +1443,11 @@ input[type='text'].infio-chat-list-dropdown-item-title-input {
} }
.infio-ai-block-content { .infio-ai-block-content {
width: 80%; width: 100%;
background: transparent; background: transparent;
border: none !important; border: none !important;
padding: 8px; padding: 8px;
padding-right: 40px; padding-right: 20px;
height: 60px; height: 60px;
resize: none; resize: none;
color: var(--text-normal); color: var(--text-normal);
@ -1462,6 +1462,10 @@ input[type='text'].infio-chat-list-dropdown-item-title-input {
} }
} }
.infio-ai-block-content::-webkit-scrollbar {
display: none;
}
.infio-ai-block-close-button { .infio-ai-block-close-button {
position: absolute; position: absolute;
top: 8px; top: 8px;
@ -1655,6 +1659,14 @@ select.infio-ai-block-model-select::-ms-expand {
} }
} }
.infio-approve-button {
background: rgba(var(--color-green-rgb), 0.7);
}
.infio-reject-button {
background: rgba(var(--color-red-rgb), 0.7);
}
.infio-accept { .infio-accept {
background: rgba(var(--color-green-rgb), 0.7); background: rgba(var(--color-green-rgb), 0.7);
box-shadow: inset 0 0 0 1000px rgba(0, 0, 0, 0.1); box-shadow: inset 0 0 0 1000px rgba(0, 0, 0, 0.1);
@ -1673,10 +1685,12 @@ select.infio-ai-block-model-select::-ms-expand {
} }
.view-actions { .view-actions {
gap: 2px;
button { button {
color: var(--text-normal); color: var(--text-normal);
font-weight: var(--font-medium); font-weight: var(--font-medium);
gap: 2px; gap: 2px;
border-radius: 0.5;
} }
} }
} }