using System.Collections.ObjectModel; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using VideoDownloader.Browser; namespace VideoDownloader.App; /// UC-K16 즐겨찾기 관리 창 — 검색·편집(M17)·삭제·HTML 내보내기/가져오기(M19). public partial class BookmarkManagerWindow : Window { private readonly ObservableCollection _source; private readonly Action _persist; public BookmarkManagerWindow(ObservableCollection bookmarks, Action persist) { InitializeComponent(); _source = bookmarks; _persist = persist; List.ItemsSource = _source; } private void SearchBox_TextChanged(object sender, TextChangedEventArgs e) { var q = SearchBox.Text?.Trim() ?? ""; var view = System.Windows.Data.CollectionViewSource.GetDefaultView(_source); view.Filter = q.Length == 0 ? null : o => o is Bookmark b && (b.Title.Contains(q, StringComparison.OrdinalIgnoreCase) || b.Url.Contains(q, StringComparison.OrdinalIgnoreCase)); } private void Edit_Click(object sender, RoutedEventArgs e) { if ((sender as FrameworkElement)?.DataContext is not Bookmark b) return; var title = InputBox("즐겨찾기 편집", "이름", b.Title); if (title == null) return; var url = InputBox("즐겨찾기 편집", "URL", b.Url); if (url == null) return; var idx = _source.IndexOf(b); if (idx >= 0) _source[idx] = new Bookmark(title.Trim(), url.Trim()); _persist(); } private void Delete_Click(object sender, RoutedEventArgs e) { if ((sender as FrameworkElement)?.DataContext is not Bookmark b) return; _source.Remove(b); _persist(); } private void List_KeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Delete && List.SelectedItem is Bookmark b) { _source.Remove(b); _persist(); e.Handled = true; } } /// UC-M19 브라우저 호환 Netscape 북마크 HTML 로 내보내기. private void Export_Click(object sender, RoutedEventArgs e) { var dlg = new Microsoft.Win32.SaveFileDialog { Filter = "북마크 HTML|*.html", FileName = "bookmarks.html" }; if (dlg.ShowDialog() != true) return; var sb = new System.Text.StringBuilder(); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine("Bookmarks

Bookmarks

"); foreach (var b in _source) sb.AppendLine($"

{System.Net.WebUtility.HtmlEncode(b.Title)}"); sb.AppendLine("

"); File.WriteAllText(dlg.FileName, sb.ToString()); } ///

Netscape HTML 에서 HREF/제목 쌍을 추출해 추가 가져오기. private void Import_Click(object sender, RoutedEventArgs e) { var dlg = new Microsoft.Win32.OpenFileDialog { Filter = "북마크 HTML|*.html;*.htm" }; if (dlg.ShowDialog() != true) return; try { var html = File.ReadAllText(dlg.FileName); var added = 0; foreach (var (url, title) in ParseNetscapeBookmarks(html)) { if (_source.Any(b => b.Url.Equals(url, StringComparison.OrdinalIgnoreCase))) continue; _source.Add(new Bookmark(title, url)); added++; } _persist(); MessageBox.Show(this, $"{added}개 항목을 가져왔습니다", "가져오기"); } catch (Exception ex) { MessageBox.Show(this, $"가져오기 실패: {ex.Message}", "가져오기", MessageBoxButton.OK, MessageBoxImage.Warning); } } internal static IEnumerable<(string Url, string Title)> ParseNetscapeBookmarks(string html) { var matches = System.Text.RegularExpressions.Regex.Matches(html, @"]*HREF=""(?[^""]+)""[^>]*>(?.*?)</A>", System.Text.RegularExpressions.RegexOptions.IgnoreCase); foreach (System.Text.RegularExpressions.Match m in matches) { var url = System.Net.WebUtility.HtmlDecode(m.Groups["url"].Value); var title = System.Text.RegularExpressions.Regex.Replace(m.Groups["title"].Value, "<[^>]+>", ""); yield return (url, System.Net.WebUtility.HtmlDecode(title).Trim()); } } private string? InputBox(string title, string label, string initial) { var box = new TextBox { Text = initial, Style = (Style)FindResource("DarkTextBox"), Margin = new Thickness(0, 6, 0, 12) }; var win = new Window { Title = title, Width = 440, SizeToContent = SizeToContent.Height, WindowStartupLocation = WindowStartupLocation.CenterOwner, Owner = this, Background = (System.Windows.Media.Brush)FindResource("WindowBg"), }; var cancel = new Button { Content = "취소", Style = (Style)FindResource("GhostButton"), Width = 80, Margin = new Thickness(0, 0, 8, 0) }; var ok = new Button { Content = "확인", Style = (Style)FindResource("PrimaryButton"), Width = 80, IsDefault = true }; cancel.Click += (_, _) => win.DialogResult = false; ok.Click += (_, _) => win.DialogResult = true; win.Content = new StackPanel { Margin = new Thickness(16) }.Also(p => { p.Children.Add(new TextBlock { Text = label, Foreground = (System.Windows.Media.Brush)FindResource("TextSecondary") }); p.Children.Add(box); p.Children.Add(new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Children = { cancel, ok }, }); }); return win.ShowDialog() == true ? box.Text : null; } private void Close_Click(object sender, RoutedEventArgs e) => Close(); } /// <summary>람다식 객체 초기화 헬퍼(BookmarkManagerWindow 입력 다이얼로그용).</summary> internal static class UiExtensions { public static T Also<T>(this T self, Action<T> init) { init(self); return self; } }