releases/VideoDownloader.App/BookmarkManagerWindow.xaml.cs

156 lines
6.4 KiB
C#

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using VideoDownloader.Browser;
namespace VideoDownloader.App;
/// <summary>UC-K16 즐겨찾기 관리 창 — 검색·편집(M17)·삭제·HTML 내보내기/가져오기(M19).</summary>
public partial class BookmarkManagerWindow : Window
{
private readonly ObservableCollection<Bookmark> _source;
private readonly Action _persist;
public BookmarkManagerWindow(ObservableCollection<Bookmark> 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;
}
}
/// <summary>UC-M19 브라우저 호환 Netscape 북마크 HTML 로 내보내기.</summary>
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("<!DOCTYPE NETSCAPE-Bookmark-file-1>");
sb.AppendLine("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">");
sb.AppendLine("<TITLE>Bookmarks</TITLE><H1>Bookmarks</H1><DL><p>");
foreach (var b in _source)
sb.AppendLine($" <DT><A HREF=\"{System.Net.WebUtility.HtmlEncode(b.Url)}\">{System.Net.WebUtility.HtmlEncode(b.Title)}</A>");
sb.AppendLine("</DL><p>");
File.WriteAllText(dlg.FileName, sb.ToString());
}
/// <summary>Netscape HTML 에서 HREF/제목 쌍을 추출해 추가 가져오기.</summary>
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,
@"<A\s[^>]*HREF=""(?<url>[^""]+)""[^>]*>(?<title>.*?)</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;
}
}