diff --git a/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs b/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs index 9b41c54..eadaac4 100644 --- a/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs +++ b/src/KeyKeeper/ViewModels/UnlockedRepositoryViewModel.cs @@ -126,6 +126,23 @@ public class UnlockedRepositoryViewModel : ViewModelBase OnPropertyChanged(nameof(Passwords)); } + public bool AddEntryToGroup(PassStoreEntry entry, PassStoreEntryGroup targetGroup) + { + PassStoreEntryPassword? pwd = FollowLinkIfNeeded(entry); + if (pwd == null) return false; + + foreach (var bl in pwd.Backlinks) + { + if (bl is PassStoreEntryLink lnk && lnk.Parent == targetGroup) + return false; + } + passStore.AddEntry(targetGroup, new PassStoreEntryLink(Guid.NewGuid(), DateTime.Now, DateTime.Now, pwd.Id, pwd)); + + HasUnsavedChanges = true; + OnPropertyChanged(nameof(Passwords)); + return true; + } + public void UpdateEntry(PassStoreEntryPassword updatedEntry) { passStore.UpdateEntry(null, updatedEntry.Id, updatedEntry); diff --git a/src/KeyKeeper/Views/RepositoryWindow.axaml b/src/KeyKeeper/Views/RepositoryWindow.axaml index 35c7472..72c8c9e 100644 --- a/src/KeyKeeper/Views/RepositoryWindow.axaml +++ b/src/KeyKeeper/Views/RepositoryWindow.axaml @@ -154,10 +154,11 @@ - + + diff --git a/src/KeyKeeper/Views/RepositoryWindow.axaml.cs b/src/KeyKeeper/Views/RepositoryWindow.axaml.cs index 77196f0..431eab1 100644 --- a/src/KeyKeeper/Views/RepositoryWindow.axaml.cs +++ b/src/KeyKeeper/Views/RepositoryWindow.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; @@ -14,6 +15,7 @@ public partial class RepositoryWindow : Window { private bool allowClose; private bool closeConfirmationShown; + private PassStoreEntry? _contextMenuEntry; public RepositoryWindow(RepositoryWindowViewModel model) { @@ -223,6 +225,66 @@ public partial class RepositoryWindow : Window } } + private void EntryContextMenu_Opening(object? sender, RoutedEventArgs args) + { + if (sender is not ContextMenu contextMenu || DataContext is not RepositoryWindowViewModel vm || + vm.CurrentPage is not UnlockedRepositoryViewModel pageVm) + return; + + _contextMenuEntry = null; + + if (contextMenu.Parent?.Parent is Border border && border.DataContext is PassStoreEntry entry) + { + _contextMenuEntry = entry; + } + + var addToGroupItem = contextMenu.Items + .OfType() + .FirstOrDefault(m => m.Name == "entryCtxMenuAddToGroup"); + + if (addToGroupItem == null) + return; + + addToGroupItem.Items.Clear(); + + var nonDefaultGroups = pageVm.PasswordGroups + .Where(g => g.GroupType != FileFormatConstants.GROUP_TYPE_DEFAULT) + .ToList(); + + EventHandler onSubmenuClick = (sender, args) => AddToGroup_Click(sender, args, _contextMenuEntry!); + foreach (var group in nonDefaultGroups) + { + var menuItem = new MenuItem + { + Header = group.DisplayName, + Tag = group + }; + menuItem.Click += onSubmenuClick; + addToGroupItem.Items.Add(menuItem); + } + } + + private void AddToGroup_Click(object? sender, RoutedEventArgs args, PassStoreEntry entry) + { + if (sender is not MenuItem item || item.Tag is not PassStoreEntryGroup targetGroup) + return; + + if (entry == null) + return; + + if (DataContext is not RepositoryWindowViewModel vm || + vm.CurrentPage is not UnlockedRepositoryViewModel pageVm) + return; + + var notificationHost = this.FindControlRecursive("NotificationHost"); + + if (pageVm.AddEntryToGroup(entry, targetGroup)) + notificationHost?.Show($"Added to {targetGroup.DisplayName}"); + else + notificationHost?.Show($"This entry is already in {targetGroup.DisplayName}!"); + _contextMenuEntry = null; + } + private async void EntryContextMenuItem_Click(object sender, RoutedEventArgs args) { if (args.Source is StyledElement s && s.DataContext is PassStoreEntry ent)