feature: add context menu for issue link in commit details panel (#651)

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo 2024-11-04 15:31:55 +08:00
parent 64860950c7
commit 163e8cc0a4
No known key found for this signature in database
24 changed files with 76 additions and 66 deletions

View file

@ -30,7 +30,7 @@ namespace SourceGit.Models
public string Arguments public string Arguments
{ {
get => _arguments; get => _arguments;
set => SetProperty(ref _arguments, value); set => SetProperty(ref _arguments, value);
} }

View file

@ -325,8 +325,8 @@ namespace SourceGit.Native
if (localMachine.OpenSubKey(@"SOFTWARE\Classes\VisualStudio.Launcher.sln\CLSID") is Microsoft.Win32.RegistryKey launcher) if (localMachine.OpenSubKey(@"SOFTWARE\Classes\VisualStudio.Launcher.sln\CLSID") is Microsoft.Win32.RegistryKey launcher)
{ {
// Get actual path to the executable // Get actual path to the executable
if (launcher.GetValue(string.Empty) is string CLSID && if (launcher.GetValue(string.Empty) is string CLSID &&
localMachine.OpenSubKey(@$"SOFTWARE\Classes\CLSID\{CLSID}\LocalServer32") is Microsoft.Win32.RegistryKey devenv && localMachine.OpenSubKey(@$"SOFTWARE\Classes\CLSID\{CLSID}\LocalServer32") is Microsoft.Win32.RegistryKey devenv &&
devenv.GetValue(string.Empty) is string localServer32) devenv.GetValue(string.Empty) is string localServer32)
{ {
return localServer32!.Trim('\"'); return localServer32!.Trim('\"');

View file

@ -387,6 +387,8 @@
<x:String x:Key="Text.InteractiveRebase" xml:space="preserve">Interactive Rebase</x:String> <x:String x:Key="Text.InteractiveRebase" xml:space="preserve">Interactive Rebase</x:String>
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">Target Branch:</x:String> <x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">Target Branch:</x:String>
<x:String x:Key="Text.InteractiveRebase.On" xml:space="preserve">On:</x:String> <x:String x:Key="Text.InteractiveRebase.On" xml:space="preserve">On:</x:String>
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">Open in Browser</x:String>
<x:String x:Key="Text.IssueLinkCM.CopyLink" xml:space="preserve">Copy Link</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">ERROR</x:String> <x:String x:Key="Text.Launcher.Error" xml:space="preserve">ERROR</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">NOTICE</x:String> <x:String x:Key="Text.Launcher.Info" xml:space="preserve">NOTICE</x:String>
<x:String x:Key="Text.Merge" xml:space="preserve">Merge Branch</x:String> <x:String x:Key="Text.Merge" xml:space="preserve">Merge Branch</x:String>

View file

@ -407,8 +407,8 @@ namespace SourceGit.Views
var menu = new ContextMenu(); var menu = new ContextMenu();
menu.Items.Add(copy); menu.Items.Add(copy);
menu.Open(TextArea.TextView);
TextArea.TextView.OpenContextMenu(menu);
e.Handled = true; e.Handled = true;
} }

View file

@ -15,7 +15,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.BranchCompare vm && sender is ChangeCollectionView view) if (DataContext is ViewModels.BranchCompare vm && sender is ChangeCollectionView view)
{ {
var menu = vm.CreateChangeContextMenu(); var menu = vm.CreateChangeContextMenu();
view.OpenContextMenu(menu); menu?.Open(view);
} }
e.Handled = true; e.Handled = true;

View file

@ -374,7 +374,7 @@ namespace SourceGit.Views
if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Remote remote }) if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Remote remote })
{ {
var menu = repo.CreateContextMenuForRemote(remote); var menu = repo.CreateContextMenuForRemote(remote);
this.OpenContextMenu(menu); menu?.Open(this);
return; return;
} }
@ -391,7 +391,7 @@ namespace SourceGit.Views
var menu = branch.IsLocal ? var menu = branch.IsLocal ?
repo.CreateContextMenuForLocalBranch(branch) : repo.CreateContextMenuForLocalBranch(branch) :
repo.CreateContextMenuForRemoteBranch(branch); repo.CreateContextMenuForRemoteBranch(branch);
this.OpenContextMenu(menu); menu?.Open(this);
} }
else if (branches.Find(x => x.IsCurrent) == null) else if (branches.Find(x => x.IsCurrent) == null)
{ {
@ -405,7 +405,7 @@ namespace SourceGit.Views
ev.Handled = true; ev.Handled = true;
}; };
menu.Items.Add(deleteMulti); menu.Items.Add(deleteMulti);
this.OpenContextMenu(menu); menu?.Open(this);
} }
} }

View file

@ -68,7 +68,7 @@ namespace SourceGit.Views
private void OnOpenWebLink(object sender, RoutedEventArgs e) private void OnOpenWebLink(object sender, RoutedEventArgs e)
{ {
if (DataContext is ViewModels.CommitDetail detail) if (DataContext is ViewModels.CommitDetail detail && sender is Control control)
{ {
var links = WebLinks; var links = WebLinks;
if (links.Count > 1) if (links.Count > 1)
@ -88,7 +88,7 @@ namespace SourceGit.Views
menu.Items.Add(item); menu.Items.Add(item);
} }
(sender as Control)?.OpenContextMenu(menu); menu?.Open(control);
} }
else if (links.Count == 1) else if (links.Count == 1)
{ {

View file

@ -16,7 +16,7 @@ namespace SourceGit.Views
DataContext is ViewModels.CommitDetail vm) DataContext is ViewModels.CommitDetail vm)
{ {
var menu = vm.CreateChangeContextMenu(selected[0]); var menu = vm.CreateChangeContextMenu(selected[0]);
view.OpenContextMenu(menu); menu?.Open(view);
} }
e.Handled = true; e.Handled = true;

View file

@ -26,7 +26,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.CommitDetail detail && sender is Grid grid && grid.DataContext is Models.Change change) if (DataContext is ViewModels.CommitDetail detail && sender is Grid grid && grid.DataContext is Models.Change change)
{ {
var menu = detail.CreateChangeContextMenu(change); var menu = detail.CreateChangeContextMenu(change);
grid.OpenContextMenu(menu); menu?.Open(grid);
} }
e.Handled = true; e.Handled = true;

View file

@ -176,7 +176,41 @@ namespace SourceGit.Views
} }
else else
{ {
Native.OS.OpenBrowser(_lastHover.Link); var point = e.GetCurrentPoint(this);
var link = _lastHover.Link;
if (point.Properties.IsLeftButtonPressed)
{
Native.OS.OpenBrowser(link);
}
else if (point.Properties.IsRightButtonPressed)
{
var open = new MenuItem();
open.Header = App.Text("IssueLinkCM.OpenInBrowser");
open.Icon = App.CreateMenuIcon("Icons.OpenWith");
open.Click += (_, ev) =>
{
ev.Handled = true;
var parentView = this.FindAncestorOfType<CommitBaseInfo>();
if (parentView is { DataContext: ViewModels.CommitDetail detail })
detail.NavigateTo(link);
};
var copy = new MenuItem();
copy.Header = App.Text("IssueLinkCM.CopyLink");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Click += (_, ev) =>
{
App.CopyText(link);
ev.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(open);
menu.Items.Add(copy);
menu.Open(this);
}
} }
e.Handled = true; e.Handled = true;

View file

@ -1,26 +0,0 @@
using System.ComponentModel;
using Avalonia.Controls;
namespace SourceGit.Views
{
public static class ContextMenuExtension
{
public static void OpenContextMenu(this Control control, ContextMenu menu)
{
if (menu == null)
return;
menu.PlacementTarget = control;
menu.Closing += OnContextMenuClosing; // Clear context menu because it is dynamic.
control.ContextMenu = menu;
control.ContextMenu?.Open();
}
private static void OnContextMenuClosing(object sender, CancelEventArgs e)
{
if (sender is ContextMenu menu && menu.PlacementTarget != null)
menu.PlacementTarget.ContextMenu = null;
}
}
}

View file

@ -706,7 +706,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.Histories histories && sender is ListBox { SelectedItems: { Count: > 0 } } list) if (DataContext is ViewModels.Histories histories && sender is ListBox { SelectedItems: { Count: > 0 } } list)
{ {
var menu = histories.MakeContextMenu(list); var menu = histories.MakeContextMenu(list);
list.OpenContextMenu(menu); menu?.Open(list);
} }
e.Handled = true; e.Handled = true;
} }

View file

@ -250,7 +250,7 @@ namespace SourceGit.Views
if (sender is Button btn && DataContext is ViewModels.Launcher launcher) if (sender is Button btn && DataContext is ViewModels.Launcher launcher)
{ {
var menu = launcher.CreateContextForWorkspace(); var menu = launcher.CreateContextForWorkspace();
btn.OpenContextMenu(menu); menu?.Open(btn);
} }
e.Handled = true; e.Handled = true;

View file

@ -234,7 +234,7 @@ namespace SourceGit.Views
if (sender is Border border && DataContext is ViewModels.Launcher vm) if (sender is Border border && DataContext is ViewModels.Launcher vm)
{ {
var menu = vm.CreateContextForPageTab(border.DataContext as ViewModels.LauncherPage); var menu = vm.CreateContextForPageTab(border.DataContext as ViewModels.LauncherPage);
border.OpenContextMenu(menu); menu?.Open(border);
} }
e.Handled = true; e.Handled = true;

View file

@ -189,7 +189,7 @@ namespace SourceGit.Views
if (sender is ListBox { SelectedItem: Models.Submodule submodule } grid && DataContext is ViewModels.Repository repo) if (sender is ListBox { SelectedItem: Models.Submodule submodule } grid && DataContext is ViewModels.Repository repo)
{ {
var menu = repo.CreateContextMenuForSubmodule(submodule.Path); var menu = repo.CreateContextMenuForSubmodule(submodule.Path);
grid.OpenContextMenu(menu); menu?.Open(grid);
} }
e.Handled = true; e.Handled = true;
@ -210,7 +210,7 @@ namespace SourceGit.Views
if (sender is ListBox { SelectedItem: Models.Worktree worktree } grid && DataContext is ViewModels.Repository repo) if (sender is ListBox { SelectedItem: Models.Worktree worktree } grid && DataContext is ViewModels.Repository repo)
{ {
var menu = repo.CreateContextMenuForWorktree(worktree); var menu = repo.CreateContextMenuForWorktree(worktree);
grid.OpenContextMenu(menu); menu?.Open(grid);
} }
e.Handled = true; e.Handled = true;

View file

@ -17,7 +17,7 @@ namespace SourceGit.Views
if (sender is Button button && DataContext is ViewModels.Repository repo) if (sender is Button button && DataContext is ViewModels.Repository repo)
{ {
var menu = repo.CreateContextMenuForExternalTools(); var menu = repo.CreateContextMenuForExternalTools();
button.OpenContextMenu(menu); menu?.Open(button);
e.Handled = true; e.Handled = true;
} }
} }
@ -72,10 +72,10 @@ namespace SourceGit.Views
private void OpenGitFlowMenu(object sender, RoutedEventArgs e) private void OpenGitFlowMenu(object sender, RoutedEventArgs e)
{ {
if (DataContext is ViewModels.Repository repo) if (DataContext is ViewModels.Repository repo && sender is Control control)
{ {
var menu = repo.CreateContextMenuForGitFlow(); var menu = repo.CreateContextMenuForGitFlow();
(sender as Control)?.OpenContextMenu(menu); menu?.Open(control);
} }
e.Handled = true; e.Handled = true;
@ -83,10 +83,10 @@ namespace SourceGit.Views
private void OpenGitLFSMenu(object sender, RoutedEventArgs e) private void OpenGitLFSMenu(object sender, RoutedEventArgs e)
{ {
if (DataContext is ViewModels.Repository repo) if (DataContext is ViewModels.Repository repo && sender is Control control)
{ {
var menu = repo.CreateContextMenuForGitLFS(); var menu = repo.CreateContextMenuForGitLFS();
(sender as Control)?.OpenContextMenu(menu); menu?.Open(control);
} }
e.Handled = true; e.Handled = true;
@ -94,10 +94,10 @@ namespace SourceGit.Views
private void OpenCustomActionMenu(object sender, RoutedEventArgs e) private void OpenCustomActionMenu(object sender, RoutedEventArgs e)
{ {
if (DataContext is ViewModels.Repository repo) if (DataContext is ViewModels.Repository repo && sender is Control control)
{ {
var menu = repo.CreateContextMenuForCustomAction(); var menu = repo.CreateContextMenuForCustomAction();
(sender as Control)?.OpenContextMenu(menu); menu?.Open(control);
} }
e.Handled = true; e.Handled = true;

View file

@ -15,7 +15,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.RevisionCompare vm && sender is ChangeCollectionView view) if (DataContext is ViewModels.RevisionCompare vm && sender is ChangeCollectionView view)
{ {
var menu = vm.CreateChangeContextMenu(); var menu = vm.CreateChangeContextMenu();
view.OpenContextMenu(menu); menu?.Open(view);
} }
e.Handled = true; e.Handled = true;

View file

@ -229,7 +229,7 @@ namespace SourceGit.Views
if (obj.Type != Models.ObjectType.Tree) if (obj.Type != Models.ObjectType.Tree)
{ {
var menu = vm.CreateRevisionFileContextMenu(obj); var menu = vm.CreateRevisionFileContextMenu(obj);
grid.OpenContextMenu(menu); menu?.Open(grid);
} }
} }

View file

@ -95,8 +95,8 @@ namespace SourceGit.Views
var menu = new ContextMenu(); var menu = new ContextMenu();
menu.Items.Add(copy); menu.Items.Add(copy);
menu.Open(TextArea.TextView);
TextArea.TextView.OpenContextMenu(menu);
e.Handled = true; e.Handled = true;
} }

View file

@ -28,7 +28,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.StashesPage vm && sender is Border border) if (DataContext is ViewModels.StashesPage vm && sender is Border border)
{ {
var menu = vm.MakeContextMenu(border.DataContext as Models.Stash); var menu = vm.MakeContextMenu(border.DataContext as Models.Stash);
border.OpenContextMenu(menu); menu?.Open(border);
} }
e.Handled = true; e.Handled = true;
} }
@ -38,7 +38,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.StashesPage vm && sender is Grid grid) if (DataContext is ViewModels.StashesPage vm && sender is Grid grid)
{ {
var menu = vm.MakeContextMenuForChange(grid.DataContext as Models.Change); var menu = vm.MakeContextMenuForChange(grid.DataContext as Models.Change);
grid.OpenContextMenu(menu); menu?.Open(grid);
} }
e.Handled = true; e.Handled = true;
} }

View file

@ -225,7 +225,7 @@ namespace SourceGit.Views
if (selected != null && DataContext is ViewModels.Repository repo) if (selected != null && DataContext is ViewModels.Repository repo)
{ {
var menu = repo.CreateContextMenuForTag(selected); var menu = repo.CreateContextMenuForTag(selected);
control.OpenContextMenu(menu); menu?.Open(control);
} }
e.Handled = true; e.Handled = true;

View file

@ -589,8 +589,8 @@ namespace SourceGit.Views
var menu = new ContextMenu(); var menu = new ContextMenu();
menu.Items.Add(copy); menu.Items.Add(copy);
menu.Open(TextArea.TextView);
TextArea.TextView.OpenContextMenu(menu);
e.Handled = true; e.Handled = true;
} }

View file

@ -117,7 +117,7 @@ namespace SourceGit.Views
if (sender is Grid { DataContext: ViewModels.RepositoryNode node } grid) if (sender is Grid { DataContext: ViewModels.RepositoryNode node } grid)
{ {
var menu = ViewModels.Welcome.Instance.CreateContextMenu(node); var menu = ViewModels.Welcome.Instance.CreateContextMenu(node);
grid.OpenContextMenu(menu); menu?.Open(grid);
e.Handled = true; e.Handled = true;
} }
} }

View file

@ -31,27 +31,27 @@ namespace SourceGit.Views
{ {
var menu = vm.CreateContextMenuForCommitMessages(); var menu = vm.CreateContextMenuForCommitMessages();
menu.Placement = PlacementMode.TopEdgeAlignedLeft; menu.Placement = PlacementMode.TopEdgeAlignedLeft;
button.OpenContextMenu(menu); menu?.Open(button);
e.Handled = true; e.Handled = true;
} }
} }
private void OnUnstagedContextRequested(object sender, ContextRequestedEventArgs e) private void OnUnstagedContextRequested(object sender, ContextRequestedEventArgs e)
{ {
if (DataContext is ViewModels.WorkingCopy vm) if (DataContext is ViewModels.WorkingCopy vm && sender is Control control)
{ {
var menu = vm.CreateContextMenuForUnstagedChanges(); var menu = vm.CreateContextMenuForUnstagedChanges();
(sender as Control)?.OpenContextMenu(menu); menu?.Open(control);
e.Handled = true; e.Handled = true;
} }
} }
private void OnStagedContextRequested(object sender, ContextRequestedEventArgs e) private void OnStagedContextRequested(object sender, ContextRequestedEventArgs e)
{ {
if (DataContext is ViewModels.WorkingCopy vm) if (DataContext is ViewModels.WorkingCopy vm && sender is Control control)
{ {
var menu = vm.CreateContextMenuForStagedChanges(); var menu = vm.CreateContextMenuForStagedChanges();
(sender as Control)?.OpenContextMenu(menu); menu?.Open(control);
e.Handled = true; e.Handled = true;
} }
} }
@ -136,10 +136,10 @@ namespace SourceGit.Views
private void OnOpenOpenAIHelper(object sender, RoutedEventArgs e) private void OnOpenOpenAIHelper(object sender, RoutedEventArgs e)
{ {
if (DataContext is ViewModels.WorkingCopy vm) if (DataContext is ViewModels.WorkingCopy vm && sender is Control control)
{ {
var menu = vm.CreateContextForOpenAI(); var menu = vm.CreateContextForOpenAI();
(sender as Button)?.OpenContextMenu(menu); menu?.Open(control);
} }
e.Handled = true; e.Handled = true;