Phase C (T2-1..T3-10): command palette, reader mode (real Readability.js, E2E), boost force-dark, screenshot, split view, vertical tabs, Glance (Alt+click), tab groups (6 colors), workspaces, speed dial, InPrivate tabs, quick find, source view, print, PiP, TTS (94 tests GREEN)

This commit is contained in:
Yun Chan 2026-08-15 15:22:44 +09:00
parent 0caa1111e7
commit 406957088f
14 changed files with 3496 additions and 7 deletions

View file

@ -38,6 +38,12 @@ public sealed class TabItem : INotifyPropertyChanged
private bool _isLoading;
public bool IsLoading { get => _isLoading; set => Set(ref _isLoading, value); }
private string? _groupName; // T2-11 탭 그룹
public string? GroupName { get => _groupName; set => Set(ref _groupName, value); }
private string? _groupColor;
public string? GroupColor { get => _groupColor; set => Set(ref _groupColor, value); }
/// <summary>마지막 활성 시각(탭 절전 판정용)</summary>
public DateTime LastActivated { get; set; } = DateTime.UtcNow;
@ -52,6 +58,13 @@ public sealed class TabItem : INotifyPropertyChanged
/// <summary>탭별 페이지 내 검색 세션 (CoreWebView2 바인딩).</summary>
public FindSession? FindSession { get; set; }
/// <summary>T2-4 리더 모드 진입 여부.</summary>
public bool IsReader { get; set; }
/// <summary>T3-3 InPrivate(사생활 보호) 탭 여부.</summary>
public bool IsPrivate { get; set; }
/// <summary>리더 진입 전 원본 URL(복귀용).</summary>
public string? ReaderOriginalUrl { get; set; }
public event PropertyChangedEventHandler? PropertyChanged;
private void Set<T>(ref T field, T value, [CallerMemberName] string? name = null)

View file

@ -162,3 +162,44 @@ public sealed class TabManager
ActivateAt(Math.Clamp(snap.ActiveIndex, 0, Math.Max(0, Tabs.Count - 1)));
}
}
// ── T2-11 탭 그룹 확장 ──
public static class TabManagerGroupExtensions
{
/// <summary>그룹 지정(null 이면 해제). 같은 그룹 탭은 인접하도록 이동.</summary>
public static void SetGroup(this TabManager m, TabItem tab, string? name, string? color)
{
if (!m.Tabs.Contains(tab)) return;
tab.GroupName = name;
tab.GroupColor = color;
if (name == null) return;
// 같은 그룹 마지막 탭 뒤로 이동
var lastIdx = -1;
for (var i = 0; i < m.Tabs.Count; i++)
if (string.Equals(m.Tabs[i].GroupName, name, StringComparison.Ordinal) && !ReferenceEquals(m.Tabs[i], tab))
lastIdx = i;
if (lastIdx >= 0) m.Move(tab, lastIdx + 1);
}
/// <summary>그룹 전체 닫기. 반환: 닫은 탭 수.</summary>
public static int CloseGroup(this TabManager m, string groupName)
{
var group = m.Tabs.Where(t => string.Equals(t.GroupName, groupName, StringComparison.Ordinal)).ToList();
foreach (var t in group) m.CloseTab(t);
return group.Count;
}
}
/// <summary>워크스페이스 스냅샷(T3-1, Zen Workspaces 패턴).</summary>
public sealed record WorkspaceSnapshot(string Name, List<string> Urls);
public static class TabManagerWorkspaceExtensions
{
/// <summary>현재 탭 세트를 워크스페이스로 캡처(현재 상태는 유지).</summary>
public static WorkspaceSnapshot CaptureWorkspace(this TabManager m, string name)
=> new(name, m.Tabs.Select(t => t.Url).ToList());
/// <summary>워크스페이스로 전환: 기존 탭 제거 후 스냅샷 복원.</summary>
public static void SwitchWorkspace(this TabManager m, WorkspaceSnapshot ws)
=> m.RestoreSession(new SessionSnapshot(ws.Urls, 0, Enumerable.Repeat(false, ws.Urls.Count).ToList()));
}