using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// 클립보드 히스토리 핸들러. "#" 프리픽스로 사용합니다. /// 예: # (빈 쿼리) → 최근 클립보드 목록 /// # hello → "hello"가 포함된 항목 필터 /// 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 CategoryFilters = new(StringComparer.OrdinalIgnoreCase) { { "url", "URL" }, { "코드", "코드" }, { "code", "코드" }, { "경로", "경로" }, { "path", "경로" }, { "핀", "핀" }, { "pin", "핀" }, }; public Task> GetItemsAsync(string query, CancellationToken ct) { var history = _historyService.History; if (history.Count == 0) { return Task.FromResult>( [ 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>(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}"); } } }