mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-11 23:06:45 +01:00
Merge pull request #34 from zadjii-msft/dev/crutkas/continueFixes
Bookmarks abstracted out of main project
This commit is contained in:
@@ -34,6 +34,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Built-in Extensions", "Buil
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Calc", "src\Exts\Microsoft.CmdPal.Ext.Calc\Microsoft.CmdPal.Ext.Calc.csproj", "{42DB35EE-1EDB-41E4-9C9F-A3520EBC5CC4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Bookmarks", "src\Exts\Microsoft.CmdPal.Ext.Bookmark\Microsoft.CmdPal.Ext.Bookmarks.csproj", "{C5BADA22-70FF-41D1-9529-28F4891316A8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -150,6 +152,14 @@ Global
|
||||
{42DB35EE-1EDB-41E4-9C9F-A3520EBC5CC4}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{42DB35EE-1EDB-41E4-9C9F-A3520EBC5CC4}.Release|x64.ActiveCfg = Release|x64
|
||||
{42DB35EE-1EDB-41E4-9C9F-A3520EBC5CC4}.Release|x64.Build.0 = Release|x64
|
||||
{C5BADA22-70FF-41D1-9529-28F4891316A8}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{C5BADA22-70FF-41D1-9529-28F4891316A8}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{C5BADA22-70FF-41D1-9529-28F4891316A8}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C5BADA22-70FF-41D1-9529-28F4891316A8}.Debug|x64.Build.0 = Debug|x64
|
||||
{C5BADA22-70FF-41D1-9529-28F4891316A8}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{C5BADA22-70FF-41D1-9529-28F4891316A8}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{C5BADA22-70FF-41D1-9529-28F4891316A8}.Release|x64.ActiveCfg = Release|x64
|
||||
{C5BADA22-70FF-41D1-9529-28F4891316A8}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -167,6 +177,7 @@ Global
|
||||
{EB13FDBD-7DD5-4E7E-8BEB-727B3C9331CB} = {B7FF739F-7716-4FC3-B622-705486187B87}
|
||||
{65E22130-6A8F-4AB7-80EC-FF75475DE821} = {B7FF739F-7716-4FC3-B622-705486187B87}
|
||||
{42DB35EE-1EDB-41E4-9C9F-A3520EBC5CC4} = {272D0E9A-8FC3-49F5-8FAD-79ABAE8AB1E4}
|
||||
{C5BADA22-70FF-41D1-9529-28F4891316A8} = {272D0E9A-8FC3-49F5-8FAD-79ABAE8AB1E4}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BC94BFC2-A741-4978-B6A4-9E01B7660E6B}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
internal sealed class AddBookmarkForm : Form
|
||||
{
|
||||
internal event TypedEventHandler<object, object?>? AddedAction;
|
||||
|
||||
public override string TemplateJson()
|
||||
{
|
||||
var json = $$"""
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"style": "text",
|
||||
"id": "name",
|
||||
"label": "Name",
|
||||
"isRequired": true,
|
||||
"errorMessage": "Name is required"
|
||||
},
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"style": "text",
|
||||
"id": "bookmark",
|
||||
"label": "URL or File Path",
|
||||
"isRequired": true,
|
||||
"errorMessage": "URL or File Path is required"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Save",
|
||||
"data": {
|
||||
"name": "name",
|
||||
"bookmark": "bookmark"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
return json;
|
||||
}
|
||||
|
||||
public override string DataJson() => throw new NotImplementedException();
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
|
||||
public override ActionResult SubmitForm(string payload)
|
||||
{
|
||||
var formInput = JsonNode.Parse(payload);
|
||||
if (formInput == null)
|
||||
{
|
||||
return ActionResult.GoHome();
|
||||
}
|
||||
|
||||
// get the name and url out of the values
|
||||
var formName = formInput["name"] ?? string.Empty;
|
||||
var formBookmark = formInput["bookmark"] ?? string.Empty;
|
||||
var hasPlaceholder = formBookmark.ToString().Contains('{') && formBookmark.ToString().Contains('}');
|
||||
|
||||
// Determine the type of the bookmark
|
||||
string bookmarkType;
|
||||
|
||||
if (formBookmark.ToString().StartsWith("http://", StringComparison.OrdinalIgnoreCase) || formBookmark.ToString().StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
bookmarkType = "web";
|
||||
}
|
||||
else if (File.Exists(formBookmark.ToString()))
|
||||
{
|
||||
bookmarkType = "file";
|
||||
}
|
||||
else if (Directory.Exists(formBookmark.ToString()))
|
||||
{
|
||||
bookmarkType = "folder";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to web if we can't determine the type
|
||||
bookmarkType = "web";
|
||||
}
|
||||
|
||||
var formData = new BookmarkData()
|
||||
{
|
||||
Name = formName.ToString(),
|
||||
Bookmark = formBookmark.ToString(),
|
||||
Type = bookmarkType,
|
||||
};
|
||||
|
||||
// Construct a new json blob with the name and url
|
||||
var jsonPath = BookmarksActionProvider.StateJsonPath();
|
||||
var data = Bookmarks.ReadFromFile(jsonPath);
|
||||
|
||||
data.Data.Add(formData);
|
||||
|
||||
Bookmarks.WriteToFile(jsonPath, data);
|
||||
|
||||
AddedAction?.Invoke(this, null);
|
||||
return ActionResult.GoHome();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
internal sealed class AddBookmarkPage : FormPage
|
||||
{
|
||||
private readonly AddBookmarkForm _addBookmark = new();
|
||||
|
||||
internal event TypedEventHandler<object, object?>? AddedAction
|
||||
{
|
||||
add => _addBookmark.AddedAction += value;
|
||||
remove => _addBookmark.AddedAction -= value;
|
||||
}
|
||||
|
||||
public override IForm[] Forms() => [_addBookmark];
|
||||
|
||||
public AddBookmarkPage()
|
||||
{
|
||||
this.Icon = new("\ued0e");
|
||||
this.Name = "Add a Bookmark";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
public class BookmarkData
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Bookmark { get; set; } = string.Empty;
|
||||
|
||||
public string Type { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
[JsonSerializable(typeof(BookmarkData))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
internal sealed partial class BookmarkDataContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
internal sealed class BookmarkPlaceholderForm : Form
|
||||
{
|
||||
private readonly List<string> _placeholderNames;
|
||||
|
||||
private readonly string _bookmark = string.Empty;
|
||||
|
||||
// TODO pass in an array of placeholders
|
||||
public BookmarkPlaceholderForm(string name, string url, string type)
|
||||
{
|
||||
_bookmark = url;
|
||||
Regex r = new Regex(Regex.Escape("{") + "(.*?)" + Regex.Escape("}"));
|
||||
MatchCollection matches = r.Matches(url);
|
||||
_placeholderNames = matches.Select(m => m.Groups[1].Value).ToList();
|
||||
}
|
||||
|
||||
public override string TemplateJson()
|
||||
{
|
||||
var inputs = _placeholderNames.Select(p =>
|
||||
{
|
||||
return $$"""
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"style": "text",
|
||||
"id": "{{p}}",
|
||||
"label": "{{p}}",
|
||||
"isRequired": true,
|
||||
"errorMessage": "{{p}} is required"
|
||||
}
|
||||
""";
|
||||
}).ToList();
|
||||
|
||||
var allInputs = string.Join(",", inputs);
|
||||
|
||||
var json = $$"""
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [
|
||||
""" + allInputs + """
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Open",
|
||||
"data": {
|
||||
"placeholder": "placeholder"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
return json;
|
||||
}
|
||||
|
||||
public override string DataJson() => throw new NotImplementedException();
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
|
||||
public override ActionResult SubmitForm(string payload)
|
||||
{
|
||||
var target = _bookmark;
|
||||
|
||||
// parse the submitted JSON and then open the link
|
||||
var formInput = JsonNode.Parse(payload);
|
||||
var formObject = formInput?.AsObject();
|
||||
if (formObject == null)
|
||||
{
|
||||
return ActionResult.GoHome();
|
||||
}
|
||||
|
||||
foreach (var (key, value) in formObject)
|
||||
{
|
||||
var placeholderString = $"{{{key}}}";
|
||||
var placeholderData = value?.ToString();
|
||||
target = target.Replace(placeholderString, placeholderData);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Uri? uri = UrlAction.GetUri(target);
|
||||
if (uri != null)
|
||||
{
|
||||
_ = Launcher.LaunchUriAsync(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw new UriFormatException("The provided URL is not valid.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error launching URL: {ex.Message}");
|
||||
}
|
||||
|
||||
return ActionResult.GoHome();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
internal sealed class BookmarkPlaceholderPage : FormPage
|
||||
{
|
||||
private readonly IForm _bookmarkPlaceholder;
|
||||
|
||||
public override IForm[] Forms() => [_bookmarkPlaceholder];
|
||||
|
||||
public BookmarkPlaceholderPage(string name, string url, string type)
|
||||
{
|
||||
_Name = name;
|
||||
Icon = new(UrlAction.IconFromUrl(url, type));
|
||||
_bookmarkPlaceholder = new BookmarkPlaceholderForm(name, url, type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
public sealed class Bookmarks
|
||||
{
|
||||
public List<BookmarkData> Data { get; set; } = [];
|
||||
|
||||
private static readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
IncludeFields = true,
|
||||
};
|
||||
|
||||
public static Bookmarks ReadFromFile(string path)
|
||||
{
|
||||
var data = new Bookmarks();
|
||||
|
||||
// if the file exists, load it and append the new item
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var jsonStringReading = File.ReadAllText(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(jsonStringReading))
|
||||
{
|
||||
data = JsonSerializer.Deserialize<Bookmarks>(jsonStringReading, _jsonOptions) ?? new Bookmarks();
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public static void WriteToFile(string path, Bookmarks data)
|
||||
{
|
||||
var jsonString = JsonSerializer.Serialize(data, _jsonOptions);
|
||||
|
||||
File.WriteAllText(BookmarksActionProvider.StateJsonPath(), jsonString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
public class BookmarksActionProvider : ICommandProvider
|
||||
{
|
||||
public string DisplayName => $"Bookmarks";
|
||||
|
||||
public IconDataType Icon => new(string.Empty);
|
||||
|
||||
private readonly List<ICommand> _commands = [];
|
||||
private readonly AddBookmarkPage _addNewCommand = new();
|
||||
|
||||
public BookmarksActionProvider()
|
||||
{
|
||||
_addNewCommand.AddedAction += AddNewCommand_AddedAction;
|
||||
}
|
||||
|
||||
private void AddNewCommand_AddedAction(object sender, object? args)
|
||||
{
|
||||
_addNewCommand.AddedAction += AddNewCommand_AddedAction;
|
||||
_commands.Clear();
|
||||
}
|
||||
|
||||
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
|
||||
public void Dispose() => throw new NotImplementedException();
|
||||
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
|
||||
|
||||
private void LoadCommands()
|
||||
{
|
||||
List<ICommand> collected = [];
|
||||
collected.Add(_addNewCommand);
|
||||
|
||||
try
|
||||
{
|
||||
var jsonFile = StateJsonPath();
|
||||
if (File.Exists(jsonFile))
|
||||
{
|
||||
var data = Bookmarks.ReadFromFile(jsonFile);
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
var items = data?.Data;
|
||||
|
||||
if (items != null)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
var nameToken = item.Name;
|
||||
var urlToken = item.Bookmark;
|
||||
var typeToken = item.Type;
|
||||
|
||||
if (nameToken == null || urlToken == null || typeToken == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = nameToken.ToString();
|
||||
var url = urlToken.ToString();
|
||||
var type = typeToken.ToString();
|
||||
|
||||
collected.Add((url.Contains('{') && url.Contains('}')) ? new BookmarkPlaceholderPage(name, url, type) : new UrlAction(name, url, type));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// debug log error
|
||||
Console.WriteLine($"Error loading commands: {ex.Message}");
|
||||
}
|
||||
|
||||
_commands.Clear();
|
||||
_commands.AddRange(collected);
|
||||
}
|
||||
|
||||
public IListItem[] TopLevelCommands()
|
||||
{
|
||||
if (_commands.Count == 0)
|
||||
{
|
||||
LoadCommands();
|
||||
}
|
||||
|
||||
return _commands.Select(action =>
|
||||
{
|
||||
var listItem = new ListItem(action);
|
||||
|
||||
// Add actions for folder types
|
||||
if (action is UrlAction urlAction && urlAction.Type == "folder")
|
||||
{
|
||||
listItem.MoreCommands = [
|
||||
new CommandContextItem(new OpenInTerminalAction(urlAction.Url))
|
||||
];
|
||||
}
|
||||
|
||||
// listItem.Subtitle = "Bookmark";
|
||||
if (action is not AddBookmarkPage)
|
||||
{
|
||||
listItem.Tags = [
|
||||
new Tag()
|
||||
{
|
||||
Text = "Bookmark",
|
||||
|
||||
// Icon = new("🔗"),
|
||||
// Color=Windows.UI.Color.FromArgb(255, 255, 0, 255)
|
||||
},
|
||||
|
||||
// new Tag() {
|
||||
// Text = "A test",
|
||||
// //Icon = new("🔗"),
|
||||
// Color=Windows.UI.Color.FromArgb(255, 255, 0, 0)
|
||||
// }
|
||||
];
|
||||
}
|
||||
|
||||
return listItem;
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
internal static string StateJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = System.IO.Path.GetDirectoryName(path) ?? string.Empty;
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return System.IO.Path.Combine(directory, "state.json");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.Bookmarks</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\extensionsdk\Microsoft.Windows.CommandPalette.Extensions.Helpers\Microsoft.CmdPal.Extensions.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
internal sealed class OpenInTerminalAction : InvokableCommand
|
||||
{
|
||||
private readonly string _folder;
|
||||
|
||||
public OpenInTerminalAction(string folder)
|
||||
{
|
||||
Name = "Open in Terminal";
|
||||
_folder = folder;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Start Windows Terminal with the specified folder
|
||||
var startInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "wt.exe",
|
||||
Arguments = $"-d \"{_folder}\"",
|
||||
UseShellExecute = true,
|
||||
};
|
||||
System.Diagnostics.Process.Start(startInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error launching Windows Terminal: {ex.Message}");
|
||||
}
|
||||
|
||||
return ActionResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(BookmarkData))]
|
||||
internal sealed partial class SourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
public class UrlAction : InvokableCommand
|
||||
{
|
||||
private bool IsContainsPlaceholder => _url.Contains('{') && _url.Contains('}');
|
||||
|
||||
public string Type { get; }
|
||||
|
||||
public string Url { get; }
|
||||
|
||||
private readonly string _url;
|
||||
|
||||
public UrlAction(string name, string url, string type)
|
||||
{
|
||||
_url = url;
|
||||
Icon = new(IconFromUrl(_url, type));
|
||||
Name = name;
|
||||
Type = type;
|
||||
Url = url;
|
||||
}
|
||||
|
||||
public override ActionResult Invoke()
|
||||
{
|
||||
var target = _url;
|
||||
try
|
||||
{
|
||||
Uri? uri = GetUri(target);
|
||||
if (uri != null)
|
||||
{
|
||||
_ = Launcher.LaunchUriAsync(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw new UriFormatException("The provided URL is not valid.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error launching URL: {ex.Message}");
|
||||
}
|
||||
|
||||
return ActionResult.Dismiss();
|
||||
}
|
||||
|
||||
internal static Uri? GetUri(string url)
|
||||
{
|
||||
Uri? uri;
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out uri))
|
||||
{
|
||||
if (!Uri.TryCreate("https://" + url, UriKind.Absolute, out uri))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
internal static string IconFromUrl(string url, string type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case "file":
|
||||
return "📄";
|
||||
case "folder":
|
||||
return "📁";
|
||||
case "web":
|
||||
default:
|
||||
// Get the base url up to the first placeholder
|
||||
var placeholderIndex = url.IndexOf('{');
|
||||
var baseString = placeholderIndex > 0 ? url.Substring(0, placeholderIndex) : url;
|
||||
try
|
||||
{
|
||||
Uri? uri = GetUri(baseString);
|
||||
if (uri != null)
|
||||
{
|
||||
var hostname = uri.Host;
|
||||
var faviconUrl = $"{uri.Scheme}://{hostname}/favicon.ico";
|
||||
return faviconUrl;
|
||||
}
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
// return "🔗";
|
||||
}
|
||||
|
||||
return "🔗";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.Calc</RootNamespace>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.Calc</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\extensionsdk\Microsoft.Windows.CommandPalette.Extensions.Helpers\Microsoft.CmdPal.Extensions.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,541 +0,0 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.Windows.CommandPalette.Extensions;
|
||||
using Microsoft.Windows.CommandPalette.Extensions.Helpers;
|
||||
using Windows.Foundation;
|
||||
using Windows.System;
|
||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||
|
||||
namespace Run.Bookmarks;
|
||||
|
||||
internal sealed class OpenInTerminalAction : InvokableCommand
|
||||
{
|
||||
private readonly string _folder;
|
||||
|
||||
public OpenInTerminalAction(string folder)
|
||||
{
|
||||
Name = "Open in Terminal";
|
||||
_folder = folder;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Start Windows Terminal with the specified folder
|
||||
var startInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "wt.exe",
|
||||
Arguments = $"-d \"{_folder}\"",
|
||||
UseShellExecute = true,
|
||||
};
|
||||
System.Diagnostics.Process.Start(startInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error launching Windows Terminal: {ex.Message}");
|
||||
}
|
||||
|
||||
return ActionResult.Dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
public class BookmarkData
|
||||
{
|
||||
public string Name = string.Empty;
|
||||
public string Bookmark = string.Empty;
|
||||
public string Type = string.Empty;
|
||||
}
|
||||
|
||||
public sealed class Bookmarks
|
||||
{
|
||||
public List<BookmarkData> Data { get; set; } = [];
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(BookmarkData))]
|
||||
internal sealed partial class SourceGenerationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(BookmarkData))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
internal sealed partial class BookmarkDataContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
|
||||
internal sealed class AddBookmarkForm : Form
|
||||
{
|
||||
internal event TypedEventHandler<object, object?>? AddedAction;
|
||||
|
||||
public override string TemplateJson()
|
||||
{
|
||||
var json = $$"""
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"style": "text",
|
||||
"id": "name",
|
||||
"label": "Name",
|
||||
"isRequired": true,
|
||||
"errorMessage": "Name is required"
|
||||
},
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"style": "text",
|
||||
"id": "bookmark",
|
||||
"label": "URL or File Path",
|
||||
"isRequired": true,
|
||||
"errorMessage": "URL or File Path is required"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Save",
|
||||
"data": {
|
||||
"name": "name",
|
||||
"bookmark": "bookmark"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
return json;
|
||||
}
|
||||
|
||||
public override string DataJson() => throw new NotImplementedException();
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
|
||||
public override ActionResult SubmitForm(string payload)
|
||||
{
|
||||
var formInput = JsonNode.Parse(payload);
|
||||
if (formInput == null)
|
||||
{
|
||||
return ActionResult.GoHome();
|
||||
}
|
||||
|
||||
// get the name and url out of the values
|
||||
var formName = formInput["name"] ?? string.Empty;
|
||||
var formBookmark = formInput["bookmark"] ?? string.Empty;
|
||||
var hasPlaceholder = formBookmark.ToString().Contains('{') && formBookmark.ToString().Contains('}');
|
||||
|
||||
// Determine the type of the bookmark
|
||||
string bookmarkType;
|
||||
|
||||
if (formBookmark.ToString().StartsWith("http://", StringComparison.OrdinalIgnoreCase) || formBookmark.ToString().StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
bookmarkType = "web";
|
||||
}
|
||||
else if (File.Exists(formBookmark.ToString()))
|
||||
{
|
||||
bookmarkType = "file";
|
||||
}
|
||||
else if (Directory.Exists(formBookmark.ToString()))
|
||||
{
|
||||
bookmarkType = "folder";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to web if we can't determine the type
|
||||
bookmarkType = "web";
|
||||
}
|
||||
|
||||
var formData = new BookmarkData()
|
||||
{
|
||||
Name = formName.ToString(),
|
||||
Bookmark = formBookmark.ToString(),
|
||||
Type = bookmarkType,
|
||||
};
|
||||
|
||||
// Construct a new json blob with the name and url
|
||||
var jsonPath = BookmarksActionProvider.StateJsonPath();
|
||||
Bookmarks data;
|
||||
|
||||
// if the file exists, load it and append the new item
|
||||
if (File.Exists(jsonPath))
|
||||
{
|
||||
var jsonStringReading = File.ReadAllText(jsonPath);
|
||||
|
||||
data = JsonSerializer.Deserialize<Bookmarks>(jsonStringReading);
|
||||
}
|
||||
else
|
||||
{
|
||||
data = new Bookmarks();
|
||||
}
|
||||
|
||||
data.Data.Add(formData);
|
||||
var options = new JsonSerializerOptions()
|
||||
{
|
||||
IncludeFields = true,
|
||||
};
|
||||
var jsonString = JsonSerializer.Serialize<Bookmarks>(data, options);
|
||||
|
||||
File.WriteAllText(BookmarksActionProvider.StateJsonPath(), jsonString);
|
||||
AddedAction?.Invoke(this, null);
|
||||
return ActionResult.GoHome();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AddBookmarkPage : FormPage
|
||||
{
|
||||
private readonly AddBookmarkForm _addBookmark = new();
|
||||
|
||||
internal event TypedEventHandler<object, object?>? AddedAction
|
||||
{
|
||||
add => _addBookmark.AddedAction += value;
|
||||
remove => _addBookmark.AddedAction -= value;
|
||||
}
|
||||
|
||||
public override IForm[] Forms() => [_addBookmark];
|
||||
|
||||
public AddBookmarkPage()
|
||||
{
|
||||
this.Icon = new("\ued0e");
|
||||
this.Name = "Add a Bookmark";
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class BookmarkPlaceholderForm : Microsoft.Windows.CommandPalette.Extensions.Helpers.Form
|
||||
{
|
||||
private readonly List<string> _placeholderNames;
|
||||
|
||||
private readonly string _bookmark = string.Empty;
|
||||
|
||||
// TODO pass in an array of placeholders
|
||||
public BookmarkPlaceholderForm(string name, string url, string type)
|
||||
{
|
||||
_bookmark = url;
|
||||
Regex r = new Regex(Regex.Escape("{") + "(.*?)" + Regex.Escape("}"));
|
||||
MatchCollection matches = r.Matches(url);
|
||||
_placeholderNames = matches.Select(m => m.Groups[1].Value).ToList();
|
||||
}
|
||||
|
||||
public override string TemplateJson()
|
||||
{
|
||||
var inputs = _placeholderNames.Select(p =>
|
||||
{
|
||||
return $$"""
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"style": "text",
|
||||
"id": "{{p}}",
|
||||
"label": "{{p}}",
|
||||
"isRequired": true,
|
||||
"errorMessage": "{{p}} is required"
|
||||
}
|
||||
""";
|
||||
}).ToList();
|
||||
|
||||
var allInputs = string.Join(",", inputs);
|
||||
|
||||
var json = $$"""
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [
|
||||
""" + allInputs + """
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Open",
|
||||
"data": {
|
||||
"placeholder": "placeholder"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
return json;
|
||||
}
|
||||
|
||||
public override string DataJson() => throw new NotImplementedException();
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
|
||||
public override ActionResult SubmitForm(string payload)
|
||||
{
|
||||
var target = _bookmark;
|
||||
|
||||
// parse the submitted JSON and then open the link
|
||||
var formInput = JsonNode.Parse(payload);
|
||||
var formObject = formInput?.AsObject();
|
||||
if (formObject == null)
|
||||
{
|
||||
return ActionResult.GoHome();
|
||||
}
|
||||
|
||||
foreach (var (key, value) in formObject)
|
||||
{
|
||||
var placeholderString = $"{{{key}}}";
|
||||
var placeholderData = value?.ToString();
|
||||
target = target.Replace(placeholderString, placeholderData);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Uri? uri = UrlAction.GetUri(target);
|
||||
if (uri != null)
|
||||
{
|
||||
_ = Launcher.LaunchUriAsync(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw new UriFormatException("The provided URL is not valid.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error launching URL: {ex.Message}");
|
||||
}
|
||||
|
||||
return ActionResult.GoHome();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class BookmarkPlaceholderPage : FormPage
|
||||
{
|
||||
private readonly IForm _bookmarkPlaceholder;
|
||||
|
||||
public override IForm[] Forms() => [_bookmarkPlaceholder];
|
||||
|
||||
public BookmarkPlaceholderPage(string name, string url, string type)
|
||||
{
|
||||
_Name = name;
|
||||
Icon = new(UrlAction.IconFromUrl(url, type));
|
||||
_bookmarkPlaceholder = new BookmarkPlaceholderForm(name, url, type);
|
||||
}
|
||||
}
|
||||
|
||||
public class UrlAction : InvokableCommand
|
||||
{
|
||||
private bool IsContainsPlaceholder => _url.Contains('{') && _url.Contains('}');
|
||||
|
||||
public string Type { get; }
|
||||
|
||||
public string Url { get; }
|
||||
|
||||
private readonly string _url;
|
||||
|
||||
public UrlAction(string name, string url, string type)
|
||||
{
|
||||
_url = url;
|
||||
Icon = new(IconFromUrl(_url, type));
|
||||
Name = name;
|
||||
Type = type;
|
||||
Url = url;
|
||||
}
|
||||
|
||||
public override ActionResult Invoke()
|
||||
{
|
||||
var target = _url;
|
||||
try
|
||||
{
|
||||
Uri? uri = GetUri(target);
|
||||
if (uri != null)
|
||||
{
|
||||
_ = Launcher.LaunchUriAsync(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw new UriFormatException("The provided URL is not valid.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error launching URL: {ex.Message}");
|
||||
}
|
||||
|
||||
return ActionResult.Dismiss();
|
||||
}
|
||||
|
||||
internal static Uri? GetUri(string url)
|
||||
{
|
||||
Uri? uri;
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out uri))
|
||||
{
|
||||
if (!Uri.TryCreate("https://" + url, UriKind.Absolute, out uri))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
internal static string IconFromUrl(string url, string type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case "file":
|
||||
return "📄";
|
||||
case "folder":
|
||||
return "📁";
|
||||
case "web":
|
||||
default:
|
||||
// Get the base url up to the first placeholder
|
||||
var placeholderIndex = url.IndexOf('{');
|
||||
var baseString = placeholderIndex > 0 ? url.Substring(0, placeholderIndex) : url;
|
||||
try
|
||||
{
|
||||
Uri? uri = GetUri(baseString);
|
||||
if (uri != null)
|
||||
{
|
||||
var hostname = uri.Host;
|
||||
var faviconUrl = $"{uri.Scheme}://{hostname}/favicon.ico";
|
||||
return faviconUrl;
|
||||
}
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
// return "🔗";
|
||||
}
|
||||
|
||||
return "🔗";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class BookmarksActionProvider : ICommandProvider
|
||||
{
|
||||
public string DisplayName => $"Bookmarks";
|
||||
|
||||
public IconDataType Icon => new(string.Empty);
|
||||
|
||||
private readonly List<ICommand> _commands = [];
|
||||
private readonly AddBookmarkPage _addNewCommand = new();
|
||||
|
||||
public BookmarksActionProvider()
|
||||
{
|
||||
_addNewCommand.AddedAction += AddNewCommand_AddedAction;
|
||||
}
|
||||
|
||||
private void AddNewCommand_AddedAction(object sender, object? args)
|
||||
{
|
||||
_addNewCommand.AddedAction += AddNewCommand_AddedAction;
|
||||
_commands.Clear();
|
||||
}
|
||||
|
||||
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
|
||||
public void Dispose() => throw new NotImplementedException();
|
||||
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
|
||||
|
||||
private void LoadCommands()
|
||||
{
|
||||
List<ICommand> collected = [];
|
||||
collected.Add(_addNewCommand);
|
||||
try
|
||||
{
|
||||
var jsonFile = StateJsonPath();
|
||||
if (File.Exists(jsonFile))
|
||||
{
|
||||
// Open state.json from the disk and read it
|
||||
var jsonString = File.ReadAllText(jsonFile);
|
||||
var options = new JsonSerializerOptions()
|
||||
{
|
||||
IncludeFields = true,
|
||||
};
|
||||
var data = JsonSerializer.Deserialize<Bookmarks>(jsonString, options);
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
var items = data?.Data;
|
||||
foreach (var item in items)
|
||||
{
|
||||
var nameToken = item.Name;
|
||||
var urlToken = item.Bookmark;
|
||||
var typeToken = item.Type;
|
||||
|
||||
if (nameToken == null || urlToken == null || typeToken == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var name = nameToken.ToString();
|
||||
var url = urlToken.ToString();
|
||||
var type = typeToken.ToString();
|
||||
collected.Add((url.Contains('{') && url.Contains('}')) ? new BookmarkPlaceholderPage(name, url, type) : new UrlAction(name, url, type));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// debug log error
|
||||
Console.WriteLine($"Error loading commands: {ex.Message}");
|
||||
}
|
||||
|
||||
_commands.Clear();
|
||||
_commands.AddRange(collected);
|
||||
}
|
||||
|
||||
public IListItem[] TopLevelCommands()
|
||||
{
|
||||
if (_commands.Count == 0)
|
||||
{
|
||||
LoadCommands();
|
||||
}
|
||||
|
||||
return _commands.Select(action =>
|
||||
{
|
||||
var listItem = new ListItem(action);
|
||||
|
||||
// Add actions for folder types
|
||||
if (action is UrlAction urlAction && urlAction.Type == "folder")
|
||||
{
|
||||
listItem.MoreCommands = [
|
||||
new CommandContextItem(new OpenInTerminalAction(urlAction.Url))
|
||||
];
|
||||
}
|
||||
|
||||
// listItem.Subtitle = "Bookmark";
|
||||
if (action is not AddBookmarkPage)
|
||||
{
|
||||
listItem.Tags = [
|
||||
new Tag()
|
||||
{
|
||||
Text = "Bookmark",
|
||||
|
||||
// Icon = new("🔗"),
|
||||
// Color=Windows.UI.Color.FromArgb(255, 255, 0, 255)
|
||||
},
|
||||
|
||||
// new Tag() {
|
||||
// Text = "A test",
|
||||
// //Icon = new("🔗"),
|
||||
// Color=Windows.UI.Color.FromArgb(255, 255, 0, 0)
|
||||
// }
|
||||
];
|
||||
}
|
||||
|
||||
return listItem;
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
internal static string StateJsonPath()
|
||||
{
|
||||
// Get the path to our exe
|
||||
var path = System.Reflection.Assembly.GetExecutingAssembly().Location;
|
||||
|
||||
// Get the directory of the exe
|
||||
var directory = System.IO.Path.GetDirectoryName(path) ?? string.Empty;
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return System.IO.Path.Combine(directory, "state.json");
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.Windows.CommandPalette.Extensions.Helpers\Microsoft.CmdPal.Extensions.Helpers.csproj" />
|
||||
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.Bookmark\Microsoft.CmdPal.Ext.Bookmarks.csproj" />
|
||||
<ProjectReference Include="..\Exts\Microsoft.CmdPal.Ext.Calc\Microsoft.CmdPal.Ext.Calc.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.Terminal.UI\Microsoft.Terminal.UI.vcxproj">
|
||||
|
||||
@@ -9,6 +9,7 @@ using CmdPal.Models;
|
||||
using DeveloperCommandPalette;
|
||||
using Microsoft.CmdPal.Common.Extensions;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.Ext.Bookmarks;
|
||||
using Microsoft.CmdPal.Ext.Calc;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
@@ -49,7 +50,7 @@ public sealed class MainViewModel
|
||||
|
||||
internal MainViewModel()
|
||||
{
|
||||
_builtInCommands.Add(new Run.Bookmarks.BookmarksActionProvider());
|
||||
_builtInCommands.Add(new BookmarksActionProvider());
|
||||
_builtInCommands.Add(new CalculatorActionProvider());
|
||||
_builtInCommands.Add(new SettingsActionProvider());
|
||||
_builtInCommands.Add(quitActionProvider);
|
||||
|
||||
Reference in New Issue
Block a user