Some checks failed
Release Gate / gate (push) Has been cancelled
- 클립보드 히스토리, 클립보드 변환, 순차 붙여넣기 실행 경로에 공통 포커스 복원 helper를 추가했습니다. - 이전 활성 창 복원, 최소 대기, Ctrl+V 주입 순서를 하나로 맞춰 포커스 누락으로 내용이 원래 창에 들어가지 않던 문제를 완화했습니다. - 관련 변경 이력을 README와 DEVELOPMENT 문서에 반영했습니다. 검증: - dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\verify\ -p:IntermediateOutputPath=obj\verify\ - 경고 0 / 오류 0
134 lines
4.4 KiB
C#
134 lines
4.4 KiB
C#
using System.Windows;
|
|
using AxCopilot.SDK;
|
|
using AxCopilot.Services;
|
|
using AxCopilot.Themes;
|
|
|
|
namespace AxCopilot.Handlers;
|
|
|
|
/// <summary>
|
|
/// 클립보드 히스토리 핸들러. "#" 프리픽스로 사용합니다.
|
|
/// 예: # (빈 쿼리) → 최근 클립보드 목록
|
|
/// # hello → "hello"가 포함된 항목 필터
|
|
/// </summary>
|
|
public class ClipboardHistoryHandler : IActionHandler
|
|
{
|
|
private readonly ClipboardHistoryService _historyService;
|
|
|
|
public string? Prefix => "#";
|
|
|
|
public PluginMetadata Metadata => new(
|
|
"ClipboardHistory",
|
|
"클립보드 히스토리 — # 뒤에 검색어 (또는 빈 입력으로 전체 보기)",
|
|
"1.0",
|
|
"AX");
|
|
|
|
public ClipboardHistoryHandler(ClipboardHistoryService historyService)
|
|
{
|
|
_historyService = historyService;
|
|
}
|
|
|
|
private static readonly Dictionary<string, string> CategoryFilters = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
{ "url", "URL" }, { "코드", "코드" }, { "code", "코드" },
|
|
{ "경로", "경로" }, { "path", "경로" }, { "핀", "핀" }, { "pin", "핀" },
|
|
};
|
|
|
|
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
|
{
|
|
var history = _historyService.History;
|
|
|
|
if (history.Count == 0)
|
|
{
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(
|
|
[
|
|
new LauncherItem(
|
|
"클립보드 히스토리가 없습니다",
|
|
"텍스트를 복사하면 이 곳에 기록됩니다",
|
|
null, null, Symbol: Symbols.History)
|
|
]);
|
|
}
|
|
|
|
var q = query.Trim().ToLowerInvariant();
|
|
|
|
string? catFilter = null;
|
|
foreach (var (prefix, cat) in CategoryFilters)
|
|
{
|
|
if (q == prefix || q.StartsWith(prefix + " "))
|
|
{
|
|
catFilter = cat;
|
|
q = q.Length > prefix.Length ? q[(prefix.Length + 1)..].Trim() : "";
|
|
break;
|
|
}
|
|
}
|
|
|
|
var filtered = history.AsEnumerable();
|
|
|
|
if (catFilter == "핀")
|
|
filtered = filtered.Where(e => e.IsPinned);
|
|
else if (catFilter != null)
|
|
filtered = filtered.Where(e => e.Category == catFilter);
|
|
|
|
if (!string.IsNullOrEmpty(q))
|
|
filtered = filtered.Where(e => e.Preview.ToLowerInvariant().Contains(q));
|
|
|
|
var sorted = filtered
|
|
.OrderByDescending(e => e.IsPinned)
|
|
.ThenByDescending(e => e.CopiedAt);
|
|
|
|
var items = sorted
|
|
.Select(e =>
|
|
{
|
|
var pinMark = e.IsPinned ? "\uD83D\uDCCC " : "";
|
|
var catMark = e.Category != "일반" ? $"[{e.Category}] " : "";
|
|
return new LauncherItem(
|
|
$"{pinMark}{e.Preview}",
|
|
$"{catMark}{e.RelativeTime} · {e.CopiedAt:MM/dd HH:mm}",
|
|
null,
|
|
e,
|
|
Symbol: e.IsPinned ? Symbols.Favorite : (e.IsText ? Symbols.History : Symbols.Picture));
|
|
})
|
|
.ToList();
|
|
|
|
if (items.Count == 0)
|
|
{
|
|
items.Add(new LauncherItem(
|
|
$"'{query}'에 해당하는 항목 없음",
|
|
"#pin #url #코드 #경로 로 필터링 가능",
|
|
null, null, Symbol: Symbols.History));
|
|
}
|
|
|
|
return Task.FromResult<IEnumerable<LauncherItem>>(items);
|
|
}
|
|
|
|
public async Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
|
{
|
|
if (item.Data is not ClipboardEntry entry) return;
|
|
|
|
try
|
|
{
|
|
_historyService.SuppressNextCapture();
|
|
_historyService.PromoteEntry(entry);
|
|
|
|
if (!entry.IsText && entry.Image != null)
|
|
{
|
|
var originalImg = ClipboardHistoryService.LoadOriginalImage(entry.OriginalImagePath);
|
|
Clipboard.SetImage(originalImg ?? entry.Image);
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(entry.Text)) return;
|
|
Clipboard.SetText(entry.Text);
|
|
|
|
var prevWindow = WindowTracker.PreviousWindow;
|
|
if (prevWindow == IntPtr.Zero) return;
|
|
|
|
await ForegroundPasteHelper.PasteClipboardAsync(prevWindow, ct, initialDelayMs: 260);
|
|
}
|
|
catch (OperationCanceledException) { }
|
|
catch (Exception ex)
|
|
{
|
|
LogService.Warn($"클립보드 히스토리 붙여넣기 실패: {ex.Message}");
|
|
}
|
|
}
|
|
}
|