diff --git a/ModernKeePass.Application/Application.csproj b/ModernKeePass.Application/Application.csproj index ad4f0f3..989b640 100644 --- a/ModernKeePass.Application/Application.csproj +++ b/ModernKeePass.Application/Application.csproj @@ -89,6 +89,7 @@ + diff --git a/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs b/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs index 1fc3e48..7f82bb8 100644 --- a/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs +++ b/ModernKeePass.Application/Common/Interfaces/IDatabaseProxy.cs @@ -45,6 +45,7 @@ namespace ModernKeePass.Application.Common.Interfaces void AddHistory(string entryId); void RestoreFromHistory(string entryId, int historyIndex); + void DeleteHistory(string entryId, int historyIndex); IEnumerable Search(string groupId, string text); } diff --git a/ModernKeePass.Application/Entry/Commands/DeleteHistory/DeleteHistoryCommand.cs b/ModernKeePass.Application/Entry/Commands/DeleteHistory/DeleteHistoryCommand.cs new file mode 100644 index 0000000..97918ec --- /dev/null +++ b/ModernKeePass.Application/Entry/Commands/DeleteHistory/DeleteHistoryCommand.cs @@ -0,0 +1,29 @@ +using MediatR; +using ModernKeePass.Application.Common.Interfaces; +using ModernKeePass.Domain.Exceptions; + +namespace ModernKeePass.Application.Entry.Commands.DeleteHistory +{ + public class DeleteHistoryCommand : IRequest + { + public string EntryId { get; set; } + public int HistoryIndex { get; set; } + + public class DeleteHistoryCommandHandler : IRequestHandler + { + private readonly IDatabaseProxy _database; + + public DeleteHistoryCommandHandler(IDatabaseProxy database) + { + _database = database; + } + + public void Handle(DeleteHistoryCommand message) + { + if (!_database.IsOpen) throw new DatabaseClosedException(); + + _database.DeleteHistory(message.EntryId, message.HistoryIndex); + } + } + } +} \ No newline at end of file diff --git a/ModernKeePass.Application/Entry/Models/EntryVm.cs b/ModernKeePass.Application/Entry/Models/EntryVm.cs index 7b31543..c712564 100644 --- a/ModernKeePass.Application/Entry/Models/EntryVm.cs +++ b/ModernKeePass.Application/Entry/Models/EntryVm.cs @@ -28,7 +28,6 @@ namespace ModernKeePass.Application.Entry.Models public bool HasExpirationDate { get; set; } public DateTimeOffset ExpirationDate { get; set; } public DateTimeOffset ModificationDate { get; set; } - public bool IsDirty { get; set; } public override string ToString() { @@ -47,7 +46,7 @@ namespace ModernKeePass.Application.Entry.Models .ForMember(d => d.Url, opts => opts.MapFrom(s => s.Url)) .ForMember(d => d.Notes, opts => opts.MapFrom(s => s.Notes)) .ForMember(d => d.AdditionalFields, opts => opts.MapFrom(s => s.AdditionalFields)) - .ForMember(d => d.History, opts => opts.MapFrom(s => s.History.OrderByDescending(h => h.LastModificationDate))) + .ForMember(d => d.History, opts => opts.MapFrom(s => s.History.Reverse())) .ForMember(d => d.HasExpirationDate, opts => opts.MapFrom(s => s.HasExpirationDate)) .ForMember(d => d.ExpirationDate, opts => opts.MapFrom(s => s.ExpirationDate)) .ForMember(d => d.ModificationDate, opts => opts.MapFrom(s => s.LastModificationDate)) diff --git a/ModernKeePass.Application/Group/Models/GroupVm.cs b/ModernKeePass.Application/Group/Models/GroupVm.cs index c7428a8..829ff96 100644 --- a/ModernKeePass.Application/Group/Models/GroupVm.cs +++ b/ModernKeePass.Application/Group/Models/GroupVm.cs @@ -9,7 +9,7 @@ using ModernKeePass.Domain.Interfaces; namespace ModernKeePass.Application.Group.Models { - public class GroupVm: IEntityVm, ISelectableModel, IMapFrom + public class GroupVm: IEntityVm, IMapFrom { public string ParentGroupId { get; set; } public string ParentGroupName { get; set; } @@ -18,7 +18,6 @@ namespace ModernKeePass.Application.Group.Models public Icon Icon { get; set; } public List SubGroups { get; set; } public List Entries { get; set; } - public bool IsSelected { get; set; } public override string ToString() { diff --git a/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs b/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs index c28a288..b8882d9 100644 --- a/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs +++ b/ModernKeePass.Infrastructure/KeePass/KeePassDatabaseClient.cs @@ -266,6 +266,12 @@ namespace ModernKeePass.Infrastructure.KeePass pwEntry.Touch(true); } + public void DeleteHistory(string entryId, int historyIndex) + { + var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true); + pwEntry.History.RemoveAt((uint)historyIndex); + } + public void UpdateGroup(string groupId) { throw new NotImplementedException(); diff --git a/ModernKeePass/Strings/en-US/CodeBehind.resw b/ModernKeePass/Strings/en-US/CodeBehind.resw index 858ffbc..169b342 100644 --- a/ModernKeePass/Strings/en-US/CodeBehind.resw +++ b/ModernKeePass/Strings/en-US/CodeBehind.resw @@ -258,4 +258,10 @@ The CSV file needs to be formatted as such: Name of the account;Login;Password;URL;Comments + + Deleting this history entry will not delete current entry. + + + Delete history entry ? + \ No newline at end of file diff --git a/ModernKeePass/Strings/fr-FR/CodeBehind.resw b/ModernKeePass/Strings/fr-FR/CodeBehind.resw index 22a3848..eecc35b 100644 --- a/ModernKeePass/Strings/fr-FR/CodeBehind.resw +++ b/ModernKeePass/Strings/fr-FR/CodeBehind.resw @@ -258,4 +258,10 @@ Le fichier CSV doit être formatté de la façon suivante: Nom du compte;Login;Mot de passe;URL;Commentaires + + Supprimer cet historique ne supprimera pas l'entrée en cours. + + + Supprimer cet historique ? + \ No newline at end of file diff --git a/ModernKeePass/ViewModels/EntryDetailVm.cs b/ModernKeePass/ViewModels/EntryDetailVm.cs index c5bef11..3bcf093 100644 --- a/ModernKeePass/ViewModels/EntryDetailVm.cs +++ b/ModernKeePass/ViewModels/EntryDetailVm.cs @@ -11,6 +11,7 @@ using ModernKeePass.Application.Database.Commands.SaveDatabase; using ModernKeePass.Application.Database.Models; using ModernKeePass.Application.Database.Queries.GetDatabase; using ModernKeePass.Application.Entry.Commands.AddHistory; +using ModernKeePass.Application.Entry.Commands.DeleteHistory; using ModernKeePass.Application.Entry.Commands.RestoreHistory; using ModernKeePass.Application.Entry.Commands.SetFieldValue; using ModernKeePass.Application.Entry.Models; @@ -44,17 +45,42 @@ namespace ModernKeePass.ViewModels public bool SpecialPatternSelected { get; set; } public bool BracketsPatternSelected { get; set; } public string CustomChars { get; set; } = string.Empty; - public string Id => _entry.Id; + public string Id => SelectedItem.Id; + + public bool IsRecycleOnDelete + { + get + { + var database = Database; + return database.IsRecycleBinEnabled && _parent.Id != database.RecycleBinId; + } + } public IEnumerable BreadCrumb => new List { _parent }; + public ObservableCollection History { get; } /// /// Determines if the Entry is current or from history /// - public bool IsSelected + public bool IsCurrentEntry => SelectedIndex == 0; + + public EntryVm SelectedItem { - get { return _isSelected; } - set { SetProperty(ref _isSelected, value); } + get { return _selectedItem; } + set + { + SetProperty(ref _selectedItem, value); + if (value != null) OnPropertyChanged(); + } + } + public int SelectedIndex + { + get { return _selectedIndex; } + set + { + SetProperty(ref _selectedIndex, value); + OnPropertyChanged(nameof(IsCurrentEntry)); + } } public double PasswordLength @@ -65,26 +91,26 @@ namespace ModernKeePass.ViewModels public string Title { - get { return _entry.Title; } + get { return SelectedItem.Title; } set { - _entry.Title = value; + SelectedItem.Title = value; SetFieldValue(nameof(Title), value).Wait(); } } public string UserName { - get { return _entry.Username; } - set { _entry.Username = value; } + get { return SelectedItem.Username; } + set { SelectedItem.Username = value; } } public string Password { - get { return _entry.Password; } + get { return SelectedItem.Password; } set { - _entry.Password = value; + SelectedItem.Password = value; SetFieldValue(nameof(Password), value).Wait(); OnPropertyChanged(nameof(Password)); OnPropertyChanged(nameof(PasswordComplexityIndicator)); @@ -93,64 +119,64 @@ namespace ModernKeePass.ViewModels public string Url { - get { return _entry.Url?.ToString(); } + get { return SelectedItem.Url?.ToString(); } set { - _entry.Url = new Uri(value); + SelectedItem.Url = new Uri(value); SetFieldValue(nameof(Url), value).Wait(); } } public string Notes { - get { return _entry.Notes; } + get { return SelectedItem.Notes; } set { - _entry.Notes = value; + SelectedItem.Notes = value; SetFieldValue(nameof(Notes), value).Wait(); } } public Symbol Icon { - get { return (Symbol)Enum.Parse(typeof(Symbol), _entry.Icon.ToString()); } + get { return (Symbol)Enum.Parse(typeof(Symbol), SelectedItem.Icon.ToString()); } set { - _entry.Icon = (Icon)Enum.Parse(typeof(Icon), value.ToString()); - SetFieldValue(nameof(Icon), _entry.Icon).Wait(); + SelectedItem.Icon = (Icon)Enum.Parse(typeof(Icon), value.ToString()); + SetFieldValue(nameof(Icon), SelectedItem.Icon).Wait(); } } public DateTimeOffset ExpiryDate { - get { return _entry.ExpirationDate; } + get { return SelectedItem.ExpirationDate; } set { if (!HasExpirationDate) return; - _entry.ExpirationDate = value.Date; - SetFieldValue("ExpirationDate", _entry.ExpirationDate).Wait(); + SelectedItem.ExpirationDate = value.Date; + SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate).Wait(); } } public TimeSpan ExpiryTime { - get { return _entry.ExpirationDate.TimeOfDay; } + get { return SelectedItem.ExpirationDate.TimeOfDay; } set { if (!HasExpirationDate) return; - _entry.ExpirationDate = _entry.ExpirationDate.Date.Add(value); - SetFieldValue("ExpirationDate", _entry.ExpirationDate).Wait(); + SelectedItem.ExpirationDate = SelectedItem.ExpirationDate.Date.Add(value); + SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate).Wait(); } } public bool HasExpirationDate { - get { return _entry.HasExpirationDate; } + get { return SelectedItem.HasExpirationDate; } set { - _entry.HasExpirationDate = value; + SelectedItem.HasExpirationDate = value; SetFieldValue(nameof(HasExpirationDate), value).Wait(); OnPropertyChanged(nameof(HasExpirationDate)); } @@ -158,29 +184,28 @@ namespace ModernKeePass.ViewModels public SolidColorBrush BackgroundColor { - get { return _entry?.BackgroundColor.ToSolidColorBrush(); } + get { return SelectedItem?.BackgroundColor.ToSolidColorBrush(); } set { - _entry.BackgroundColor = value.ToColor(); - SetFieldValue(nameof(BackgroundColor), _entry.BackgroundColor).Wait(); + SelectedItem.BackgroundColor = value.ToColor(); + SetFieldValue(nameof(BackgroundColor), SelectedItem.BackgroundColor).Wait(); } } public SolidColorBrush ForegroundColor { - get { return _entry?.ForegroundColor.ToSolidColorBrush(); } + get { return SelectedItem?.ForegroundColor.ToSolidColorBrush(); } set { - _entry.ForegroundColor = value.ToColor(); - SetFieldValue(nameof(ForegroundColor), _entry.ForegroundColor).Wait(); + SelectedItem.ForegroundColor = value.ToColor(); + SetFieldValue(nameof(ForegroundColor), SelectedItem.ForegroundColor).Wait(); } } - public ObservableCollection History { get; } public bool IsEditMode { - get { return IsSelected && _isEditMode; } + get { return IsCurrentEntry && _isEditMode; } set { SetProperty(ref _isEditMode, value); } } @@ -199,11 +224,12 @@ namespace ModernKeePass.ViewModels private readonly IMediator _mediator; private readonly GroupVm _parent; - private EntryVm _entry; + private EntryVm _selectedItem; + private int _selectedIndex; private bool _isEditMode; private bool _isRevealPassword; private double _passwordLength = 25; - private bool _isSelected; + private bool _isDirty; public EntryDetailVm() { } @@ -212,14 +238,14 @@ namespace ModernKeePass.ViewModels public EntryDetailVm(string entryId, IMediator mediator) { _mediator = mediator; - _entry = _mediator.Send(new GetEntryQuery { Id = entryId }).GetAwaiter().GetResult(); - _parent = _mediator.Send(new GetGroupQuery { Id = _entry.ParentGroupId }).GetAwaiter().GetResult(); - History = new ObservableCollection {_entry}; - foreach (var entry in _entry.History) + SelectedItem = _mediator.Send(new GetEntryQuery { Id = entryId }).GetAwaiter().GetResult(); + _parent = _mediator.Send(new GetGroupQuery { Id = SelectedItem.ParentGroupId }).GetAwaiter().GetResult(); + History = new ObservableCollection { SelectedItem }; + foreach (var entry in SelectedItem.History) { History.Add(entry); } - IsSelected = true; + SelectedIndex = 0; SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty); GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword()); @@ -247,41 +273,41 @@ namespace ModernKeePass.ViewModels public async Task MarkForDelete(string recycleBinTitle) { - await _mediator.Send(new DeleteEntryCommand {EntryId = Id, ParentGroupId = _entry.ParentGroupId, RecycleBinName = recycleBinTitle}); + await _mediator.Send(new DeleteEntryCommand {EntryId = Id, ParentGroupId = SelectedItem.ParentGroupId, RecycleBinName = recycleBinTitle}); } public async Task Move(GroupVm destination) { - await _mediator.Send(new AddEntryCommand { ParentGroup = destination, Entry = _entry }); - await _mediator.Send(new RemoveEntryCommand { ParentGroup = _parent, Entry = _entry }); + await _mediator.Send(new AddEntryCommand { ParentGroup = destination, Entry = SelectedItem }); + await _mediator.Send(new RemoveEntryCommand { ParentGroup = _parent, Entry = SelectedItem }); } public async Task SetFieldValue(string fieldName, object value) { await _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = fieldName, FieldValue = value }); ((RelayCommand)SaveCommand).RaiseCanExecuteChanged(); - _entry.IsDirty = true; + _isDirty = true; } public async Task AddHistory() { - if (_entry.IsDirty) await _mediator.Send(new AddHistoryCommand { EntryId = Id }); + if (_isDirty) await _mediator.Send(new AddHistoryCommand { EntryId = Id }); } - - internal void SetEntry(EntryVm entry, int index) - { - _entry = entry; - IsSelected = index == 0; - OnPropertyChanged(); - } - + private async Task RestoreHistory() { - var index = History.IndexOf(_entry); - var entryToRestore = History[index]; - SetEntry(entryToRestore, 0); - await _mediator.Send(new RestoreHistoryCommand { EntryId = Id, HistoryIndex = index }); - History.Insert(0, entryToRestore); + await _mediator.Send(new RestoreHistoryCommand { EntryId = Id, HistoryIndex = History.Count - SelectedIndex - 1 }); + History.Insert(0, SelectedItem); + SelectedIndex = 0; + ((RelayCommand)SaveCommand).RaiseCanExecuteChanged(); + } + + public async Task DeleteHistory() + { + await _mediator.Send(new DeleteHistoryCommand { EntryId = Id, HistoryIndex = History.Count - SelectedIndex - 1 }); + History.RemoveAt(SelectedIndex); + SelectedIndex = 0; + ((RelayCommand)SaveCommand).RaiseCanExecuteChanged(); } private async Task SaveChanges() @@ -289,7 +315,7 @@ namespace ModernKeePass.ViewModels await AddHistory(); await _mediator.Send(new SaveDatabaseCommand()); ((RelayCommand)SaveCommand).RaiseCanExecuteChanged(); - _entry.IsDirty = false; + _isDirty = false; } } } diff --git a/ModernKeePass/ViewModels/RecentVm.cs b/ModernKeePass/ViewModels/RecentVm.cs index 246ae42..3084bbc 100644 --- a/ModernKeePass/ViewModels/RecentVm.cs +++ b/ModernKeePass/ViewModels/RecentVm.cs @@ -1,6 +1,5 @@ using System.Collections.ObjectModel; using System.Linq; -using System.Threading.Tasks; using System.Windows.Input; using Microsoft.Extensions.DependencyInjection; using ModernKeePass.Application.Common.Interfaces; diff --git a/ModernKeePass/Views/EntryDetailPage.xaml b/ModernKeePass/Views/EntryDetailPage.xaml index e87892a..de553c5 100644 --- a/ModernKeePass/Views/EntryDetailPage.xaml +++ b/ModernKeePass/Views/EntryDetailPage.xaml @@ -388,7 +388,11 @@ - + @@ -409,7 +413,7 @@ - + @@ -418,8 +422,8 @@ - - + + @@ -430,7 +434,7 @@ - + @@ -438,8 +442,8 @@ - - + + @@ -458,11 +462,11 @@ - + - + @@ -529,21 +533,16 @@ + RestoreCommand="{Binding RestoreCommand}" + DeleteButtonClick="TopMenu_OnDeleteButtonClick"> - - - - - - diff --git a/ModernKeePass/Views/EntryDetailPage.xaml.cs b/ModernKeePass/Views/EntryDetailPage.xaml.cs index cab2573..0139688 100644 --- a/ModernKeePass/Views/EntryDetailPage.xaml.cs +++ b/ModernKeePass/Views/EntryDetailPage.xaml.cs @@ -1,5 +1,4 @@ using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; using ModernKeePass.Common; using ModernKeePass.Models; @@ -22,7 +21,7 @@ namespace ModernKeePass.Views /// gestion de la durée de vie des processus /// public NavigationHelper NavigationHelper { get; } - + public EntryDetailPage() { InitializeComponent(); @@ -65,19 +64,35 @@ namespace ModernKeePass.Views VisualStateManager.GoToState(TopMenu, e.NewSize.Width < 800 ? "Collapsed" : "Overflowed", true); } - private void HamburgerMenuUserControl_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + private async void TopMenu_OnDeleteButtonClick(object sender, RoutedEventArgs e) { - var listView = sender as ListView; - if (listView == null) return; - var index = listView.SelectedIndex; - switch (index) + var resource = new ResourceHelper(); + if (Model.IsCurrentEntry) { - case -1: - return; - default: - var entry = listView.SelectedItem as Application.Entry.Models.EntryVm; - Model.SetEntry(entry, index); - break; + var isRecycleOnDelete = Model.IsRecycleOnDelete; + + var message = isRecycleOnDelete + ? resource.GetResourceValue("EntryRecyclingConfirmation") + : resource.GetResourceValue("EntryDeletingConfirmation"); + await MessageDialogHelper.ShowActionDialog(resource.GetResourceValue("EntityDeleteTitle"), message, + resource.GetResourceValue("EntityDeleteActionButton"), + resource.GetResourceValue("EntityDeleteCancelButton"), async a => + { + var text = isRecycleOnDelete ? resource.GetResourceValue("EntryRecycled") : resource.GetResourceValue("EntryDeleted"); + //ToastNotificationHelper.ShowMovedToast(Entity, _resource.GetResourceValue("EntityDeleting"), text); + await Model.MarkForDelete(resource.GetResourceValue("RecycleBinTitle")); + NavigationHelper.GoBack(); + }, null); + } + else + { + await MessageDialogHelper.ShowActionDialog(resource.GetResourceValue("HistoryDeleteTitle"), resource.GetResourceValue("HistoryDeleteDescription"), + resource.GetResourceValue("EntityDeleteActionButton"), + resource.GetResourceValue("EntityDeleteCancelButton"), async a => + { + //ToastNotificationHelper.ShowMovedToast(Entity, _resource.GetResourceValue("EntityDeleting"), text); + await Model.DeleteHistory(); + }, null); } } } diff --git a/ModernKeePass/Views/UserControls/HamburgerMenuUserControl.xaml b/ModernKeePass/Views/UserControls/HamburgerMenuUserControl.xaml index a8cd6c0..a295250 100644 --- a/ModernKeePass/Views/UserControls/HamburgerMenuUserControl.xaml +++ b/ModernKeePass/Views/UserControls/HamburgerMenuUserControl.xaml @@ -15,7 +15,8 @@ { })); + public int SelectedIndex + { + get { return (int)GetValue(SelectedIndexProperty); } + set { SetValue(SelectedIndexProperty, value); } + } + public static readonly DependencyProperty SelectedIndexProperty = + DependencyProperty.Register( + nameof(SelectedIndex), + typeof(int), + typeof(HamburgerMenuUserControl), + new PropertyMetadata(-1, (o, args) => { })); + public bool IsOpen { get { return (bool)GetValue(IsOpenProperty); } diff --git a/ModernKeePass/appMetadata/en-us/baselisting/releaseNotes.txt b/ModernKeePass/appMetadata/en-us/baselisting/releaseNotes.txt index 5c60c70..2cbde5e 100644 --- a/ModernKeePass/appMetadata/en-us/baselisting/releaseNotes.txt +++ b/ModernKeePass/appMetadata/en-us/baselisting/releaseNotes.txt @@ -1,4 +1,5 @@ Database corruption issues should now be a thing of the past ! Added the ability to move entries and groups Edits are now in a popup instead of inline +Allows restoring and deleting from entry history Updated KeePass lib to version 2.44 \ No newline at end of file