From 3bb92e2af64e6ffb88d1ebd453e06baa0387de5b Mon Sep 17 00:00:00 2001 From: Tortoise <195262249+Testudinidae@users.noreply.github.com> Date: Thu, 28 Aug 2025 22:55:37 -0700 Subject: [PATCH 1/8] refactor(ui): centralize visible text/icons at Item layer --- .../Commands/OpenHomePageCommand.cs | 6 +- .../Commands/SearchWebCommand.cs | 6 +- .../Forms/AddShortcutForm.cs | 32 +-- .../Pages/AddShortcutPage.cs | 14 +- .../WebSearchShortcut/Pages/SearchWebPage.cs | 64 ++++-- .../WebSearchShortcut/Properties/Icons.cs | 11 +- .../Properties/Resources.Designer.cs | 195 +++++++++++------- .../Properties/Resources.fr.resx | 93 +++++---- .../Properties/Resources.resx | 93 +++++---- .../Properties/Resources.zh-CN.resx | 93 +++++---- .../Properties/Resources.zh-TW.resx | 93 +++++---- .../WebSearchShortcutCommandsProvider.cs | 44 +++- 12 files changed, 451 insertions(+), 293 deletions(-) diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/OpenHomePageCommand.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/OpenHomePageCommand.cs index 57b0b41..b4f12e4 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/OpenHomePageCommand.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/OpenHomePageCommand.cs @@ -1,7 +1,5 @@ using Microsoft.CommandPalette.Extensions.Toolkit; using WebSearchShortcut.Browsers; -using WebSearchShortcut.Helpers; -using WebSearchShortcut.Properties; namespace WebSearchShortcut.Commands; @@ -12,8 +10,8 @@ internal sealed partial class OpenHomePageCommand : InvokableCommand internal OpenHomePageCommand(WebSearchShortcutDataEntry shortcut) { - Name = StringFormatter.Format(Resources.OpenHomePage_NameTemplate, new() { ["engine"] = shortcut.Name }); - Icon = Icons.Search; + Name = $"[UNBOUND] {nameof(OpenHomePageCommand)}.{nameof(Name)} required - shortcut='{shortcut.Name}'"; + _shortcut = shortcut; _browserInfo = new BrowserExecutionInfo(shortcut); } diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs index 8539b71..e306e19 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs @@ -1,7 +1,5 @@ using Microsoft.CommandPalette.Extensions.Toolkit; using WebSearchShortcut.Browsers; -using WebSearchShortcut.Helpers; -using WebSearchShortcut.Properties; namespace WebSearchShortcut.Commands; @@ -14,8 +12,8 @@ internal sealed partial class SearchWebCommand : InvokableCommand public SearchWebCommand(WebSearchShortcutDataEntry shortcut, string query) { - Name = StringFormatter.Format(Resources.SearchQuery_NameTemplate, new() { ["engine"] = shortcut.Name, ["query"] = query }); - Icon = Icons.Search; + Name = $"[UNBOUND] {nameof(SearchWebCommand)}.{nameof(Name)} required - shortcut='{shortcut.Name}', query='{query}'"; + _query = query; _shortcut = shortcut; _browserInfo = new BrowserExecutionInfo(shortcut); diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs index 71b2935..04624ff 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs @@ -32,29 +32,29 @@ public AddShortcutForm(WebSearchShortcutDataEntry? shortcut) { "id": "name", "type": "Input.Text", - "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_NameLabel, AppJsonSerializerContext.Default.String)}}, + "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_Name_Label, AppJsonSerializerContext.Default.String)}}, "value": {{JsonSerializer.Serialize(name, AppJsonSerializerContext.Default.String)}}, "isRequired": true, - "errorMessage": {{JsonSerializer.Serialize(Resources.AddShortcutForm_NameErrorMessage, AppJsonSerializerContext.Default.String)}} + "errorMessage": {{JsonSerializer.Serialize(Resources.AddShortcutForm_Name_ErrorMessage, AppJsonSerializerContext.Default.String)}} }, { "id": "url", "type": "Input.Text", "style": "Url", - "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_UrlLabel, AppJsonSerializerContext.Default.String)}}, - "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_UrlPlaceholder, AppJsonSerializerContext.Default.String)}}, + "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_Url_Label, AppJsonSerializerContext.Default.String)}}, + "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_Url_Placeholder, AppJsonSerializerContext.Default.String)}}, "value": {{JsonSerializer.Serialize(url, AppJsonSerializerContext.Default.String)}}, "isRequired": true, - "errorMessage": {{JsonSerializer.Serialize(Resources.AddShortcutForm_UrlErrorMessage, AppJsonSerializerContext.Default.String)}} + "errorMessage": {{JsonSerializer.Serialize(Resources.AddShortcutForm_Url_ErrorMessage, AppJsonSerializerContext.Default.String)}} }, { "id": "suggestionProvider", "type": "Input.ChoiceSet", - "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_SuggestionProviderLabel, AppJsonSerializerContext.Default.String)}}, - "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_SuggestionProviderPlaceholder, AppJsonSerializerContext.Default.String)}}, + "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_SuggestionProvider_Label, AppJsonSerializerContext.Default.String)}}, + "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_SuggestionProvider_Placeholder, AppJsonSerializerContext.Default.String)}}, "choices": [ { - "title": {{JsonSerializer.Serialize(Resources.AddShortcutForm_SuggestionProviderNone, AppJsonSerializerContext.Default.String)}}, + "title": {{JsonSerializer.Serialize(Resources.AddShortcutForm_SuggestionProvider_None, AppJsonSerializerContext.Default.String)}}, "value": "" }, {{SuggestionsRegistry.ProviderNames.Select(key => $$""" @@ -71,8 +71,8 @@ public AddShortcutForm(WebSearchShortcutDataEntry? shortcut) "type": "Input.Text", "style": "text", "id": "replaceWhitespace", - "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_ReplaceWhitespaceLabel, AppJsonSerializerContext.Default.String)}}, - "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_ReplaceWhitespacePlaceholder, AppJsonSerializerContext.Default.String)}}, + "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_ReplaceWhitespace_Label, AppJsonSerializerContext.Default.String)}}, + "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_ReplaceWhitespace_Placeholder, AppJsonSerializerContext.Default.String)}}, "value": {{JsonSerializer.Serialize(replaceWhitespace, AppJsonSerializerContext.Default.String)}}, "errorMessage": "// Just for space between items" }, @@ -80,19 +80,19 @@ public AddShortcutForm(WebSearchShortcutDataEntry? shortcut) "type": "Input.Text", "style": "text", "id": "homePage", - "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_HomepageLabel, AppJsonSerializerContext.Default.String)}}, - "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_HomepagePlaceholder, AppJsonSerializerContext.Default.String)}}, + "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_Homepage_Label, AppJsonSerializerContext.Default.String)}}, + "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_Homepage_Placeholder, AppJsonSerializerContext.Default.String)}}, "value": {{JsonSerializer.Serialize(homePage, AppJsonSerializerContext.Default.String)}}, "errorMessage": "// Just for space between items" }, { "id": "browserPath", "type": "Input.ChoiceSet", - "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_BrowserPathLabel, AppJsonSerializerContext.Default.String)}}, + "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_BrowserPath_Label, AppJsonSerializerContext.Default.String)}}, "placeholder": {{JsonSerializer.Serialize(browserPath, AppJsonSerializerContext.Default.String)}}, "choices": [ { - "title": {{JsonSerializer.Serialize(Resources.AddShortcutForm_BrowserPathDefault, AppJsonSerializerContext.Default.String)}}, + "title": {{JsonSerializer.Serialize(Resources.AddShortcutForm_BrowserPath_Default, AppJsonSerializerContext.Default.String)}}, "value": "" }, {{BrowserDiscovery.GetAllInstalledBrowsers() @@ -112,8 +112,8 @@ public AddShortcutForm(WebSearchShortcutDataEntry? shortcut) "id": "browserArgs", "type": "Input.Text", "style": "text", - "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_BrowserArgsLabel, AppJsonSerializerContext.Default.String)}}, - "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_BrowserArgsPlaceholder, AppJsonSerializerContext.Default.String)}}, + "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_BrowserArgs_Label, AppJsonSerializerContext.Default.String)}}, + "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_BrowserArgs_Placeholder, AppJsonSerializerContext.Default.String)}}, "value": {{JsonSerializer.Serialize(browserArgs, AppJsonSerializerContext.Default.String)}}, "errorMessage": "// Just for space between items" } diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/AddShortcutPage.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/AddShortcutPage.cs index c740058..343c503 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/AddShortcutPage.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/AddShortcutPage.cs @@ -11,16 +11,14 @@ internal sealed partial class AddShortcutPage : ContentPage public AddShortcutPage(WebSearchShortcutDataEntry? shortcut) { - var name = shortcut?.Name ?? string.Empty; - var url = shortcut?.Url ?? string.Empty; - var isAdd = string.IsNullOrEmpty(name) && string.IsNullOrEmpty(url); - - _addShortcutForm = new AddShortcutForm(shortcut); + bool isAdd = shortcut is null; Id = "WebSearchShortcut.AddShortcut"; - Icon = IconHelpers.FromRelativePath("Assets\\SearchAdd.png"); - Title = isAdd ? Resources.AddShortcut_AddTitle : Resources.SearchShortcut_EditTitle; - Name = isAdd ? Resources.AddShortcut_AddName : Resources.SearchShortcut_EditName; + Title = isAdd ? Resources.AddShortcutPage_Title_Add : Resources.AddShortcutPage_Title_Edit; + Name = $"[UNBOUND] {nameof(AddShortcutPage)}.{nameof(Name)} required - shortcut={(shortcut is null ? "null" : $"'{shortcut.Name}'")}"; + Icon = isAdd ? IconHelpers.FromRelativePath("Assets\\SearchAdd.png") : Icons.Edit; + + _addShortcutForm = new AddShortcutForm(shortcut); } internal event TypedEventHandler? AddedCommand diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs index 76ffcaf..6ed01ab 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs @@ -14,9 +14,13 @@ namespace WebSearchShortcut; internal sealed partial class SearchWebPage : DynamicListPage { private readonly WebSearchShortcutDataEntry _shortcut; - private readonly IListItem _openHomePageItem; + + private readonly IListItem _openHomepageListItem; + private readonly IContextItem _openHomepageContextItem; + private IListItem[] _items = []; private IListItem[] _suggestionItems = []; + private int _lastUpdateSearchTextEpoch; private readonly Lock _swapSuggestionsCancellationSourceLock = new(); private readonly Lock _renderLock = new(); @@ -25,17 +29,29 @@ internal sealed partial class SearchWebPage : DynamicListPage public SearchWebPage(WebSearchShortcutDataEntry shortcut) { - _shortcut = shortcut; - Id = shortcut.Id; - Name = shortcut.Name; + Title = StringFormatter.Format(Resources.SearchWebPage_TitleTemplate, new() { ["shortcut"] = shortcut.Name }); + Name = $"[UNBOUND] {nameof(SearchWebPage)}.{nameof(Name)} required - shortcut='{shortcut.Name}'"; Icon = IconService.GetIconInfo(shortcut); - _openHomePageItem = new ListItem(new OpenHomePageCommand(shortcut)) + + _shortcut = shortcut; + + var openHomepagecommand = new OpenHomePageCommand(shortcut) + { + Name = StringFormatter.Format(Resources.OpenHomepageItem_NameTemplate, new() { ["shortcut"] = shortcut.Name }) + }; + _openHomepageListItem = new ListItem(openHomepagecommand) { - Title = StringFormatter.Format(Resources.OpenHomePage_TitleTemplate, new() { ["engine"] = Name }) + Title = StringFormatter.Format(Resources.OpenHomepageItem_TitleTemplate, new() { ["shortcut"] = shortcut.Name }), + Icon = Icons.Home + }; + _openHomepageContextItem = new CommandContextItem(openHomepagecommand) + { + Title = StringFormatter.Format(Resources.OpenHomepageItem_TitleTemplate, new() { ["shortcut"] = shortcut.Name }), + Icon = Icons.Home }; - _items = [_openHomePageItem]; + _items = [_openHomepageListItem]; } public override IListItem[] GetItems() => Volatile.Read(ref _items); @@ -55,6 +71,7 @@ public override async void UpdateSearchText(string oldSearch, string newSearch) if (currentEpoch != Volatile.Read(ref _lastUpdateSearchTextEpoch)) { currentCancellationSource?.Dispose(); + return; } @@ -73,7 +90,7 @@ public override async void UpdateSearchText(string oldSearch, string newSearch) { UpdateSuggestionItems([], currentEpoch); - RenderItems([_openHomePageItem], currentEpoch); + RenderItems([_openHomepageListItem], currentEpoch); return; } @@ -108,7 +125,8 @@ public override async void UpdateSearchText(string oldSearch, string newSearch) currentCancellationSource!.Dispose(); } - if (currentEpoch != Volatile.Read(ref _lastUpdateSearchTextEpoch)) return; + if (currentEpoch != Volatile.Read(ref _lastUpdateSearchTextEpoch)) + return; UpdateSuggestionItems(suggestionItems, currentEpoch); @@ -149,11 +167,17 @@ private ListItem[] BuildPrimaryItems(string searchText) { return [ - new ListItem(new SearchWebCommand(_shortcut, searchText)) + new ListItem( + new SearchWebCommand(_shortcut, searchText) + { + Name = StringFormatter.Format(Resources.SearchQueryItem_NameTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = searchText }), + } + ) { - Title = searchText, - Subtitle = StringFormatter.Format(Resources.SearchQuery_SubtitleTemplate, new() { ["engine"] = Name, ["query"] = searchText }), - MoreCommands = [new CommandContextItem(new OpenHomePageCommand(_shortcut))] + Title = StringFormatter.Format(Resources.SearchQueryItem_TitleTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = searchText }), + Subtitle = StringFormatter.Format(Resources.SearchQueryItem_SubtitleTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = searchText }), + Icon = Icons.Search, + MoreCommands = [_openHomepageContextItem] } ]; } @@ -167,12 +191,18 @@ private async Task FetchSuggestionItemsAsync(string searchText, Canc return [ - .. suggestions.Select(suggestion => new ListItem(new SearchWebCommand(_shortcut, suggestion.Title)) + .. suggestions.Select(suggestion => new ListItem( + new SearchWebCommand(_shortcut, suggestion.Title) + { + Name = StringFormatter.Format(Resources.SearchQueryItem_NameTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = suggestion.Title }) + } + ) { - Title = suggestion.Title, - Subtitle = suggestion.Description ?? StringFormatter.Format(Resources.SearchQuery_SubtitleTemplate, new() { ["engine"] = Name, ["query"] = suggestion.Title }), + Title = StringFormatter.Format(Resources.SearchQueryItem_TitleTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = suggestion.Title }), + Subtitle = suggestion.Description ?? StringFormatter.Format(Resources.SearchQueryItem_SubtitleTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = suggestion.Title }), + Icon = Icons.Search, TextToSuggest = suggestion.Title, - MoreCommands = [new CommandContextItem(new OpenHomePageCommand(_shortcut))] + MoreCommands = [_openHomepageContextItem] }) ]; } diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs index 9dbb021..702120f 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs @@ -9,8 +9,13 @@ namespace WebSearchShortcut.Properties; /// /// Provides commonly used icons for the WebSearchShortcut application /// -public static class Icons +internal static class Icons { + /// + /// Default fallback icon for links + /// + public static IconInfo Link { get; } = new("🔗"); + /// /// Edit icon (pencil) /// @@ -22,9 +27,9 @@ public static class Icons public static IconInfo Delete { get; } = new("\uE74D"); /// - /// Default fallback icon for links + /// Homepage icon /// - public static IconInfo Link { get; } = new("🔗"); + public static IconInfo Home { get; } = new("\uE80F"); /// /// Search icon diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs index 33a9abf..6b0e6fc 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs @@ -60,111 +60,93 @@ internal Resources() { } } - /// - /// Looks up a localized string similar to Add Search Shortcut. - /// - internal static string AddShortcut_AddName { - get { - return ResourceManager.GetString("AddShortcut_AddName", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Add Search Shortcut. - /// - internal static string AddShortcut_AddTitle { - get { - return ResourceManager.GetString("AddShortcut_AddTitle", resourceCulture); - } - } - /// /// Looks up a localized string similar to Browser Arguments (Optional). /// - internal static string AddShortcutForm_BrowserArgsLabel { + internal static string AddShortcutForm_BrowserArgs_Label { get { - return ResourceManager.GetString("AddShortcutForm_BrowserArgsLabel", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_BrowserArgs_Label", resourceCulture); } } /// /// Looks up a localized string similar to Browser launch arguments, use %1 for URL. /// - internal static string AddShortcutForm_BrowserArgsPlaceholder { + internal static string AddShortcutForm_BrowserArgs_Placeholder { get { - return ResourceManager.GetString("AddShortcutForm_BrowserArgsPlaceholder", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_BrowserArgs_Placeholder", resourceCulture); } } /// /// Looks up a localized string similar to Default. /// - internal static string AddShortcutForm_BrowserPathDefault { + internal static string AddShortcutForm_BrowserPath_Default { get { - return ResourceManager.GetString("AddShortcutForm_BrowserPathDefault", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_BrowserPath_Default", resourceCulture); } } /// /// Looks up a localized string similar to Browser. /// - internal static string AddShortcutForm_BrowserPathLabel { + internal static string AddShortcutForm_BrowserPath_Label { get { - return ResourceManager.GetString("AddShortcutForm_BrowserPathLabel", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_BrowserPath_Label", resourceCulture); } } /// /// Looks up a localized string similar to Custom Home Page (Optional). /// - internal static string AddShortcutForm_HomepageLabel { + internal static string AddShortcutForm_Homepage_Label { get { - return ResourceManager.GetString("AddShortcutForm_HomepageLabel", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_Homepage_Label", resourceCulture); } } /// /// Looks up a localized string similar to Use domain by default. /// - internal static string AddShortcutForm_HomepagePlaceholder { + internal static string AddShortcutForm_Homepage_Placeholder { get { - return ResourceManager.GetString("AddShortcutForm_HomepagePlaceholder", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_Homepage_Placeholder", resourceCulture); } } /// /// Looks up a localized string similar to Name is required. /// - internal static string AddShortcutForm_NameErrorMessage { + internal static string AddShortcutForm_Name_ErrorMessage { get { - return ResourceManager.GetString("AddShortcutForm_NameErrorMessage", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_Name_ErrorMessage", resourceCulture); } } /// /// Looks up a localized string similar to Name. /// - internal static string AddShortcutForm_NameLabel { + internal static string AddShortcutForm_Name_Label { get { - return ResourceManager.GetString("AddShortcutForm_NameLabel", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_Name_Label", resourceCulture); } } /// /// Looks up a localized string similar to Replace Whitespace (Optional). /// - internal static string AddShortcutForm_ReplaceWhitespaceLabel { + internal static string AddShortcutForm_ReplaceWhitespace_Label { get { - return ResourceManager.GetString("AddShortcutForm_ReplaceWhitespaceLabel", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_ReplaceWhitespace_Label", resourceCulture); } } /// /// Looks up a localized string similar to Specify which character(s) to replace a space. /// - internal static string AddShortcutForm_ReplaceWhitespacePlaceholder { + internal static string AddShortcutForm_ReplaceWhitespace_Placeholder { get { - return ResourceManager.GetString("AddShortcutForm_ReplaceWhitespacePlaceholder", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_ReplaceWhitespace_Placeholder", resourceCulture); } } @@ -180,135 +162,198 @@ internal static string AddShortcutForm_Save { /// /// Looks up a localized string similar to Suggestion Provider. /// - internal static string AddShortcutForm_SuggestionProviderLabel { + internal static string AddShortcutForm_SuggestionProvider_Label { get { - return ResourceManager.GetString("AddShortcutForm_SuggestionProviderLabel", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_SuggestionProvider_Label", resourceCulture); } } /// /// Looks up a localized string similar to None. /// - internal static string AddShortcutForm_SuggestionProviderNone { + internal static string AddShortcutForm_SuggestionProvider_None { get { - return ResourceManager.GetString("AddShortcutForm_SuggestionProviderNone", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_SuggestionProvider_None", resourceCulture); } } /// /// Looks up a localized string similar to Invalid Suggestion Provider. /// - internal static string AddShortcutForm_SuggestionProviderPlaceholder { + internal static string AddShortcutForm_SuggestionProvider_Placeholder { get { - return ResourceManager.GetString("AddShortcutForm_SuggestionProviderPlaceholder", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_SuggestionProvider_Placeholder", resourceCulture); } } /// /// Looks up a localized string similar to URL is required. /// - internal static string AddShortcutForm_UrlErrorMessage { + internal static string AddShortcutForm_Url_ErrorMessage { get { - return ResourceManager.GetString("AddShortcutForm_UrlErrorMessage", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_Url_ErrorMessage", resourceCulture); } } /// /// Looks up a localized string similar to URL. /// - internal static string AddShortcutForm_UrlLabel { + internal static string AddShortcutForm_Url_Label { get { - return ResourceManager.GetString("AddShortcutForm_UrlLabel", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_Url_Label", resourceCulture); } } /// /// Looks up a localized string similar to Use %s in the URL for the search term. /// - internal static string AddShortcutForm_UrlPlaceholder { + internal static string AddShortcutForm_Url_Placeholder { get { - return ResourceManager.GetString("AddShortcutForm_UrlPlaceholder", resourceCulture); + return ResourceManager.GetString("AddShortcutForm_Url_Placeholder", resourceCulture); } } /// - /// Looks up a localized string similar to Open {engine}. + /// Looks up a localized string similar to Add Search Shortcut. /// - internal static string OpenHomePage_NameTemplate { + internal static string AddShortcutItem_Name { get { - return ResourceManager.GetString("OpenHomePage_NameTemplate", resourceCulture); + return ResourceManager.GetString("AddShortcutItem_Name", resourceCulture); } } /// - /// Looks up a localized string similar to Open {engine}. + /// Looks up a localized string similar to Add Search Shortcut. /// - internal static string OpenHomePage_TitleTemplate { + internal static string AddShortcutItem_Title { get { - return ResourceManager.GetString("OpenHomePage_TitleTemplate", resourceCulture); + return ResourceManager.GetString("AddShortcutItem_Title", resourceCulture); } } /// - /// Looks up a localized string similar to Search for "{query}". + /// Looks up a localized string similar to Add Search Shortcut. /// - internal static string SearchQuery_NameTemplate { + internal static string AddShortcutPage_Title_Add { get { - return ResourceManager.GetString("SearchQuery_NameTemplate", resourceCulture); + return ResourceManager.GetString("AddShortcutPage_Title_Add", resourceCulture); } } /// - /// Looks up a localized string similar to Search {engine} for "{query}". + /// Looks up a localized string similar to Edit Search Shortcut. /// - internal static string SearchQuery_SubtitleTemplate { + internal static string AddShortcutPage_Title_Edit { get { - return ResourceManager.GetString("SearchQuery_SubtitleTemplate", resourceCulture); + return ResourceManager.GetString("AddShortcutPage_Title_Edit", resourceCulture); } } /// /// Looks up a localized string similar to Delete. /// - internal static string SearchShortcut_DeleteName { + internal static string DeleteShortcutItem_TitleTemplate { get { - return ResourceManager.GetString("SearchShortcut_DeleteName", resourceCulture); + return ResourceManager.GetString("DeleteShortcutItem_TitleTemplate", resourceCulture); } } /// - /// Looks up a localized string similar to Delete. + /// Looks up a localized string similar to Edit Search Shortcut. /// - internal static string SearchShortcut_DeleteTitle { + internal static string EditShortcutItem_NameTemplate { get { - return ResourceManager.GetString("SearchShortcut_DeleteTitle", resourceCulture); + return ResourceManager.GetString("EditShortcutItem_NameTemplate", resourceCulture); } } /// /// Looks up a localized string similar to Edit Search Shortcut. /// - internal static string SearchShortcut_EditName { + internal static string EditShortcutItem_TitleTemplate { get { - return ResourceManager.GetString("SearchShortcut_EditName", resourceCulture); + return ResourceManager.GetString("EditShortcutItem_TitleTemplate", resourceCulture); } } /// - /// Looks up a localized string similar to Edit Search Shortcut. + /// Looks up a localized string similar to Open {shortcut}. + /// + internal static string OpenHomepageItem_NameTemplate { + get { + return ResourceManager.GetString("OpenHomepageItem_NameTemplate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open {shortcut}. + /// + internal static string OpenHomepageItem_TitleTemplate { + get { + return ResourceManager.GetString("OpenHomepageItem_TitleTemplate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search for "{query}". + /// + internal static string SearchQueryItem_NameTemplate { + get { + return ResourceManager.GetString("SearchQueryItem_NameTemplate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search {shortcut} for "{query}". + /// + internal static string SearchQueryItem_SubtitleTemplate { + get { + return ResourceManager.GetString("SearchQueryItem_SubtitleTemplate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {query}. + /// + internal static string SearchQueryItem_TitleTemplate { + get { + return ResourceManager.GetString("SearchQueryItem_TitleTemplate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {shortcut}. + /// + internal static string SearchWebPage_TitleTemplate { + get { + return ResourceManager.GetString("SearchWebPage_TitleTemplate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {shortcut}. + /// + internal static string ShortcutItem_NameTemplate { + get { + return ResourceManager.GetString("ShortcutItem_NameTemplate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search {shortcut}. /// - internal static string SearchShortcut_EditTitle { + internal static string ShortcutItem_SubtitleTemplate { get { - return ResourceManager.GetString("SearchShortcut_EditTitle", resourceCulture); + return ResourceManager.GetString("ShortcutItem_SubtitleTemplate", resourceCulture); } } /// - /// Looks up a localized string similar to Search {engine}. + /// Looks up a localized string similar to {shortcut}. /// - internal static string SearchShortcut_SubtitleTemplate { + internal static string ShortcutItem_TitleTemplate { get { - return ResourceManager.GetString("SearchShortcut_SubtitleTemplate", resourceCulture); + return ResourceManager.GetString("ShortcutItem_TitleTemplate", resourceCulture); } } diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx index 02d563f..9794f37 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx @@ -120,88 +120,103 @@ WebSearchShortcut - + Ajouter un raccourci de recherche - + Ajouter un raccourci de recherche - + + Ajouter un raccourci de recherche + + + Modifier le raccourci de recherche + + Nom - + Le nom est obligatoire - + URL - + Utilisez %s dans l'URL pour le terme de recherche - + L'URL est obligatoire - + Fournisseur de suggestions - + Fournisseur de suggestions invalide - + Aucun - - Remplacer les espaces (Optionnel) - - - Spécifiez quel(s) caractère(s) remplace(nt) un espace - - + Page d'accueil personnalisée (Optionnel) - + Utiliser le domaine par défaut - - Arguments du navigateur (Optionnel) + + Remplacer les espaces (Optionnel) - - Arguments de lancement du navigateur, utilisez %1 pour l'URL + + Spécifiez quel(s) caractère(s) remplace(nt) un espace - + Navigateur - + Par défaut + + Arguments du navigateur (Optionnel) + + + Arguments de lancement du navigateur, utilisez %1 pour l'URL + Enregistrer - - Rechercher sur {engine} + + {shortcut} + + + Rechercher sur {shortcut} - + + {shortcut} + + Modifier le raccourci de recherche - + Modifier le raccourci de recherche - + Supprimer - - Supprimer + + {shortcut} - - Ouvrir {engine} + + Ouvrir {shortcut} - - Ouvrir {engine} + + Ouvrir {shortcut} - - Rechercher "{query}" + + {query} - - Rechercher "{query}" sur {engine} + + Rechercher "{query}" sur {shortcut} + + + Rechercher "{query}" \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx index dddca41..2473c59 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx @@ -120,88 +120,103 @@ WebSearchShortcut - + Add Search Shortcut - + Add Search Shortcut - + + Add Search Shortcut + + + Edit Search Shortcut + + Name - + Name is required - + URL - + Use %s in the URL for the search term - + URL is required - + Suggestion Provider - + Invalid Suggestion Provider - + None - - Replace Whitespace (Optional) - - - Specify which character(s) to replace a space - - + Custom Home Page (Optional) - + Use domain by default - - Browser Arguments (Optional) + + Replace Whitespace (Optional) - - Browser launch arguments, use %1 for URL + + Specify which character(s) to replace a space - + Browser - + Default + + Browser Arguments (Optional) + + + Browser launch arguments, use %1 for URL + Save - - Search {engine} + + {shortcut} + + + Search {shortcut} - + + {shortcut} + + Edit Search Shortcut - + Edit Search Shortcut - + Delete - - Delete + + {shortcut} - - Open {engine} + + Open {shortcut} - - Open {engine} + + Open {shortcut} - - Search for "{query}" + + {query} - - Search {engine} for "{query}" + + Search {shortcut} for "{query}" + + + Search for "{query}" \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx index cb2c3dd..728e370 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx @@ -120,88 +120,103 @@ WebSearchShortcut - + 添加搜索捷径(Shortcut) - + 添加搜索捷径 - + + 添加搜索捷径 + + + 编辑搜索捷径 + + 名称 - + 名称为必填项 - + URL - + 在 URL 中使用 %s 作为搜索词占位符 - + URL 为必填项 - + 搜索建议来源 - + 无效的搜索建议来源 - + - - 替换空格(可选) - - - 指定用于替换空格的字符 - - + 自定义主页(可选) - + 默认使用主域名 - - 浏览器参数(可选) + + 替换空格(可选) - - 浏览器启动参数,使用 %1 作为 URL 占位符 + + 指定用于替换空格的字符 - + 浏览器 - + 默认 + + 浏览器参数(可选) + + + 浏览器启动参数,使用 %1 作为 URL 占位符 + 保存 - - 搜索 {engine} + + {shortcut} + + + 搜索 {shortcut} - + + {shortcut} + + 编辑搜索捷径 - + 编辑搜索捷径 - + 删除 - - 删除 + + {shortcut} - - 打开 {engine} + + 打开 {shortcut} - - 打开 {engine} + + 打开 {shortcut} - - 搜索“{query}” + + {query} - - 在 {engine} 中搜索“{query}” + + 在 {shortcut} 中搜索“{query}” + + + 搜索“{query}” \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx index 3dc1931..c0f1794 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx @@ -120,88 +120,103 @@ 網頁搜尋捷徑 - + +新增搜尋捷徑 - + 新增搜尋捷徑 - + + 新增搜尋捷徑 + + + 編輯搜尋捷徑 + + 名稱 - + 必須填寫名稱 - + 網址 - + 搜尋引擎的網址,請用 %s 取代關鍵字的位置 - + 必須填寫網址 - + 關鍵字建議來源 - + 無效的關鍵字建議來源 - + - - 替換空白字元(可選) - - - 搜尋關鍵字中的空白會被替換為這裡指定的字元 - - + 自訂首頁(可選) - + 預設為網域 - - 瀏覽器參數(可選) + + 替換空白字元(可選) - - 瀏覽器啟動參數,請用 %1 取代網址的位置 + + 搜尋關鍵字中的空白會被替換為這裡指定的字元 - + 瀏覽器 - + 使用者預設瀏覽器 + + 瀏覽器參數(可選) + + + 瀏覽器啟動參數,請用 %1 取代網址的位置 + 儲存 - - 在 {engine} 上搜尋 + + {shortcut} + + + 在 {shortcut} 上搜尋 - + + {shortcut} + + 編輯搜尋捷徑 - + 編輯搜尋捷徑 - + 刪除搜尋捷徑 - - 刪除搜尋捷徑 + + {shortcut} - - 開啟 {engine} 首頁 + + 開啟 {shortcut} 首頁 - - 開啟 {engine} 首頁 + + 開啟 {shortcut} 首頁 - - 搜尋「{query}」 + + {query} - - 在 {engine} 上搜尋「{query}」 + + 在 {shortcut} 上搜尋「{query}」 + + + 搜尋「{query}」 \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs index c1bcf71..4855166 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs @@ -17,7 +17,7 @@ namespace WebSearchShortcut; public partial class WebSearchShortcutCommandsProvider : CommandProvider { - private readonly AddShortcutPage _addShortcutPage = new(null); + private readonly ICommandItem _addShortcutItem; private ICommandItem[] _topLevelCommands = []; private Storage? _storage; @@ -26,7 +26,16 @@ public WebSearchShortcutCommandsProvider() DisplayName = Resources.WebSearchShortcut_DisplayName; Icon = IconHelpers.FromRelativePath("Assets\\Search.png"); - _addShortcutPage.AddedCommand += AddNewCommand_AddedCommand; + var addShortcutPage = new AddShortcutPage(null) + { + Name = Resources.AddShortcutItem_Name + }; + addShortcutPage.AddedCommand += AddNewCommand_AddedCommand; + _addShortcutItem = new CommandItem(addShortcutPage) + { + Title = Resources.AddShortcutItem_Title, + Icon = IconHelpers.FromRelativePath("Assets\\SearchAdd.png") + }; } public override ICommandItem[] TopLevelCommands() @@ -91,7 +100,7 @@ private void SaveAndRefresh() private void ReloadCommands() { - List items = [new CommandItem(_addShortcutPage)]; + List items = [_addShortcutItem]; if (_storage is null) { @@ -122,13 +131,20 @@ private void LoadShortcutFromFile() private CommandItem CreateCommandItem(WebSearchShortcutDataEntry shortcut) { - var editShortcutPage = new AddShortcutPage(shortcut); + var editShortcutPage = new AddShortcutPage(shortcut) + { + Name = StringFormatter.Format(Resources.EditShortcutItem_NameTemplate, new() { ["shortcut"] = shortcut.Name }), + }; editShortcutPage.AddedCommand += Edit_AddedCommand; - var editCommand = new CommandContextItem(editShortcutPage) { Icon = Icons.Edit }; + var editCommand = new CommandContextItem(editShortcutPage) + { + Title = StringFormatter.Format(Resources.EditShortcutItem_TitleTemplate, new() { ["shortcut"] = shortcut.Name }), + Icon = Icons.Edit + }; var deleteCommand = new CommandContextItem( - title: Resources.SearchShortcut_DeleteTitle, - name: Resources.SearchShortcut_DeleteName, + title: StringFormatter.Format(Resources.DeleteShortcutItem_TitleTemplate, new() { ["shortcut"] = shortcut.Name }), + name: $"[UNREACHABLE] DeleteCommand.Name - shortcut='{shortcut.Name}'", action: () => { if (_storage != null) @@ -140,15 +156,23 @@ private CommandItem CreateCommandItem(WebSearchShortcutDataEntry shortcut) SaveAndRefresh(); } }, - result: CommandResult.KeepOpen()) + result: CommandResult.KeepOpen() + ) { Icon = Icons.Delete, IsCritical = true }; - var commandItem = new CommandItem(new SearchWebPage(shortcut)) + var commandItem = new CommandItem( + new SearchWebPage(shortcut) + { + Name = StringFormatter.Format(Resources.ShortcutItem_NameTemplate, new() { ["shortcut"] = shortcut.Name }) + } + ) { - Subtitle = StringFormatter.Format(Resources.SearchShortcut_SubtitleTemplate, new() { ["engine"] = shortcut.Name }), + Title = StringFormatter.Format(Resources.ShortcutItem_TitleTemplate, new() { ["shortcut"] = shortcut.Name }), + Subtitle = StringFormatter.Format(Resources.ShortcutItem_SubtitleTemplate, new() { ["shortcut"] = shortcut.Name }), + Icon = IconService.GetIconInfo(shortcut), MoreCommands = [editCommand, deleteCommand] }; From 4ba9fba205a0d8844f1238207a9aa7b464d00782 Mon Sep 17 00:00:00 2001 From: Tortoise <195262249+Testudinidae@users.noreply.github.com> Date: Sat, 30 Aug 2025 08:40:06 -0700 Subject: [PATCH 2/8] feat(icons): add Logo and AddShortcut; replace direct asset calls --- .../WebSearchShortcut/Pages/AddShortcutPage.cs | 2 +- .../WebSearchShortcut/Properties/Icons.cs | 15 +++++++++++++++ .../WebSearchShortcutCommandsProvider.cs | 4 ++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/AddShortcutPage.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/AddShortcutPage.cs index 343c503..f587ff4 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/AddShortcutPage.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/AddShortcutPage.cs @@ -16,7 +16,7 @@ public AddShortcutPage(WebSearchShortcutDataEntry? shortcut) Id = "WebSearchShortcut.AddShortcut"; Title = isAdd ? Resources.AddShortcutPage_Title_Add : Resources.AddShortcutPage_Title_Edit; Name = $"[UNBOUND] {nameof(AddShortcutPage)}.{nameof(Name)} required - shortcut={(shortcut is null ? "null" : $"'{shortcut.Name}'")}"; - Icon = isAdd ? IconHelpers.FromRelativePath("Assets\\SearchAdd.png") : Icons.Edit; + Icon = isAdd ? Icons.AddShortcut : Icons.EditShortcut; _addShortcutForm = new AddShortcutForm(shortcut); } diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs index 702120f..f1a1a14 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs @@ -11,6 +11,21 @@ namespace WebSearchShortcut.Properties; /// internal static class Icons { + /// + /// Extension logo icon + /// + public static IconInfo Logo { get; } = IconHelpers.FromRelativePath("Assets\\Search.png"); + + /// + /// "Add Shortcut" icon + /// + public static IconInfo AddShortcut { get; } = IconHelpers.FromRelativePath("Assets\\SearchAdd.png"); + + /// + /// "Edit Shortcut" icon + /// + public static IconInfo EditShortcut { get; } = new("\uE70F"); + /// /// Default fallback icon for links /// diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs index 4855166..773fe1b 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs @@ -24,7 +24,7 @@ public partial class WebSearchShortcutCommandsProvider : CommandProvider public WebSearchShortcutCommandsProvider() { DisplayName = Resources.WebSearchShortcut_DisplayName; - Icon = IconHelpers.FromRelativePath("Assets\\Search.png"); + Icon = Icons.Logo; var addShortcutPage = new AddShortcutPage(null) { @@ -34,7 +34,7 @@ public WebSearchShortcutCommandsProvider() _addShortcutItem = new CommandItem(addShortcutPage) { Title = Resources.AddShortcutItem_Title, - Icon = IconHelpers.FromRelativePath("Assets\\SearchAdd.png") + Icon = Icons.AddShortcut }; } From 8274bc8ee2d3117bbd42b466bed36056e5adbada Mon Sep 17 00:00:00 2001 From: Tortoise <195262249+Testudinidae@users.noreply.github.com> Date: Tue, 2 Sep 2025 02:10:03 -0700 Subject: [PATCH 3/8] ui(AddShortcutForm): Reorder AddShortcutForm sections/fields for a more logical flow and consistent UX. --- .../Forms/AddShortcutForm.cs | 43 +++++++++---------- .../WebSearchShortcutDataEntry.cs | 4 +- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs index 04624ff..f673dd9 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs @@ -57,32 +57,32 @@ public AddShortcutForm(WebSearchShortcutDataEntry? shortcut) "title": {{JsonSerializer.Serialize(Resources.AddShortcutForm_SuggestionProvider_None, AppJsonSerializerContext.Default.String)}}, "value": "" }, - {{SuggestionsRegistry.ProviderNames.Select(key => $$""" +{{SuggestionsRegistry.ProviderNames.Select(key => $$""" { "title": {{JsonSerializer.Serialize(key, AppJsonSerializerContext.Default.String)}}, "value": {{JsonSerializer.Serialize(key, AppJsonSerializerContext.Default.String)}} } - """).Aggregate((a, b) => a + "," + b)}} +""") + .Aggregate((a, b) => a + ",\n" + b)}} ], "value": {{JsonSerializer.Serialize(suggestionProvider, AppJsonSerializerContext.Default.String)}}, "errorMessage": "// Just for space between items" }, { + "id": "homePage", "type": "Input.Text", - "style": "text", - "id": "replaceWhitespace", - "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_ReplaceWhitespace_Label, AppJsonSerializerContext.Default.String)}}, - "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_ReplaceWhitespace_Placeholder, AppJsonSerializerContext.Default.String)}}, - "value": {{JsonSerializer.Serialize(replaceWhitespace, AppJsonSerializerContext.Default.String)}}, + "style": "Url", + "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_Homepage_Label, AppJsonSerializerContext.Default.String)}}, + "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_Homepage_Placeholder, AppJsonSerializerContext.Default.String)}}, + "value": {{JsonSerializer.Serialize(homePage, AppJsonSerializerContext.Default.String)}}, "errorMessage": "// Just for space between items" }, { + "id": "replaceWhitespace", "type": "Input.Text", - "style": "text", - "id": "homePage", - "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_Homepage_Label, AppJsonSerializerContext.Default.String)}}, - "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_Homepage_Placeholder, AppJsonSerializerContext.Default.String)}}, - "value": {{JsonSerializer.Serialize(homePage, AppJsonSerializerContext.Default.String)}}, + "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_ReplaceWhitespace_Label, AppJsonSerializerContext.Default.String)}}, + "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_ReplaceWhitespace_Placeholder, AppJsonSerializerContext.Default.String)}}, + "value": {{JsonSerializer.Serialize(replaceWhitespace, AppJsonSerializerContext.Default.String)}}, "errorMessage": "// Just for space between items" }, { @@ -95,15 +95,15 @@ public AddShortcutForm(WebSearchShortcutDataEntry? shortcut) "title": {{JsonSerializer.Serialize(Resources.AddShortcutForm_BrowserPath_Default, AppJsonSerializerContext.Default.String)}}, "value": "" }, - {{BrowserDiscovery.GetAllInstalledBrowsers() - .Where(b => !string.IsNullOrWhiteSpace(b.Path)) - .Select(b => $$""" - { - "title": {{JsonSerializer.Serialize(b.Name, AppJsonSerializerContext.Default.String)}}, - "value": {{JsonSerializer.Serialize(b.Path, AppJsonSerializerContext.Default.String)}} - } - """) - .Aggregate((a, b) => a + "," + b)}} +{{BrowserDiscovery.GetAllInstalledBrowsers() + .Where(b => !string.IsNullOrWhiteSpace(b.Path)) + .Select(b => $$""" + { + "title": {{JsonSerializer.Serialize(b.Name, AppJsonSerializerContext.Default.String)}}, + "value": {{JsonSerializer.Serialize(b.Path, AppJsonSerializerContext.Default.String)}} + } +""") + .Aggregate((a, b) => a + ",\n" + b)}} ], "value": {{JsonSerializer.Serialize(browserPath, AppJsonSerializerContext.Default.String)}}, "errorMessage": "// Just for space between items" @@ -111,7 +111,6 @@ public AddShortcutForm(WebSearchShortcutDataEntry? shortcut) { "id": "browserArgs", "type": "Input.Text", - "style": "text", "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_BrowserArgs_Label, AppJsonSerializerContext.Default.String)}}, "placeholder": {{JsonSerializer.Serialize(Resources.AddShortcutForm_BrowserArgs_Placeholder, AppJsonSerializerContext.Default.String)}}, "value": {{JsonSerializer.Serialize(browserArgs, AppJsonSerializerContext.Default.String)}}, diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutDataEntry.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutDataEntry.cs index eedc7aa..bcc784b 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutDataEntry.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutDataEntry.cs @@ -12,9 +12,9 @@ internal sealed class WebSearchShortcutDataEntry public string Url { get; set; } = string.Empty; // public string[]? Urls { get; set; } public string? SuggestionProvider { get; set; } - public string? ReplaceWhitespace { get; set; } - public string? IconUrl { get; set; } public string? HomePage { get; set; } + public string? IconUrl { get; set; } + public string? ReplaceWhitespace { get; set; } public string? BrowserPath { get; set; } public string? BrowserArgs { get; set; } From e36d14f3a7b139aca710fbf2df96e9334d783232 Mon Sep 17 00:00:00 2001 From: Tortoise <195262249+Testudinidae@users.noreply.github.com> Date: Mon, 18 Aug 2025 23:44:49 -0700 Subject: [PATCH 4/8] feat: save user search queries and display history in search --- .../Commands/SearchWebCommand.cs | 3 + .../WebSearchShortcut/History/HistoryEntry.cs | 16 +++ .../History/HistoryService.cs | 113 ++++++++++++++++++ .../History/HistoryStorage.cs | 71 +++++++++++ .../WebSearchShortcut/JSONContext.cs | 3 + .../WebSearchShortcut/Pages/SearchWebPage.cs | 43 ++++++- .../WebSearchShortcut/Properties/Icons.cs | 5 + 7 files changed, 251 insertions(+), 3 deletions(-) create mode 100644 CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryEntry.cs create mode 100644 CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs create mode 100644 CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryStorage.cs diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs index e306e19..a380f77 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs @@ -1,5 +1,6 @@ using Microsoft.CommandPalette.Extensions.Toolkit; using WebSearchShortcut.Browsers; +using WebSearchShortcut.History; namespace WebSearchShortcut.Commands; @@ -28,6 +29,8 @@ public override CommandResult Invoke() return CommandResult.KeepOpen(); } + HistoryService.Add(_shortcut.Name, _query); + // if (_settingsManager.ShowHistory != Resources.history_none) // { // _settingsManager.SaveHistory(new HistoryItem(Arguments, DateTime.Now)); diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryEntry.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryEntry.cs new file mode 100644 index 0000000..0ad470a --- /dev/null +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryEntry.cs @@ -0,0 +1,16 @@ +using System; +using System.Text.Json.Serialization; + +namespace WebSearchShortcut.History; + +internal sealed record HistoryEntry +{ + public string Query { get; init; } + public DateTimeOffset Timestamp { get; init; } + + [JsonConstructor] + public HistoryEntry(string query, DateTimeOffset timestamp) + => (Query, Timestamp) = (query, timestamp); + + public HistoryEntry(string query) : this(query, DateTimeOffset.UtcNow) { } +} diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs new file mode 100644 index 0000000..4d2da3f --- /dev/null +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using Microsoft.CommandPalette.Extensions; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace WebSearchShortcut.History; + +internal static class HistoryService +{ + public static string HistoryFilePath + { + get + { + var directory = Utilities.BaseSettingsPath("WebSearchShortcut"); + + Directory.CreateDirectory(directory); + + return Path.Combine(directory, "WebSearchShortcut_history.json"); + } + } + + private static readonly HistoryStorage _cache = new(); + private static readonly Dictionary _shortcutQueriesMap = new(StringComparer.OrdinalIgnoreCase); + private static readonly Lock _lock = new(); + + static HistoryService() + { + Reload(); + } + + public static string[] Get(string shortcutName) + { + lock (_lock) + { + return [.. _shortcutQueriesMap.GetValueOrDefault(shortcutName, [])]; + } + } + + public static void Add(string shortcutName, string query) + { + lock (_lock) + { + if (!_cache.Data.TryGetValue(shortcutName, out var entries) || entries is null) + _cache.Data[shortcutName] = entries = []; + + entries.Insert(0, new HistoryEntry(query)); + + RebuildShortcutQueriesMap(); + + Save(); + + ExtensionHost.LogMessage($"[WebSearchShortcut] History: Add Query shortcut=\"{shortcutName}\" query=\"{query}\""); + } + } + + public static void Reload() + { + lock (_lock) + { + HistoryStorage storage; + try + { + storage = HistoryStorage.ReadFromFile(HistoryFilePath); + } + catch (Exception ex) + { + ExtensionHost.LogMessage(new LogMessage() { Message = $"[WebSearchShortcut] History: Reload failed: {ex}", State = MessageState.Error }); + + return; + } + + _cache.Data = storage?.Data ?? new Dictionary>(StringComparer.OrdinalIgnoreCase); + + RebuildShortcutQueriesMap(); + + ExtensionHost.LogMessage($"[WebSearchShortcut] History: Reload succeeded"); + } + } + + private static void Save() + { + try + { + HistoryStorage.WriteToFile(HistoryFilePath, _cache); + } + catch (Exception ex) + { + ExtensionHost.LogMessage(new LogMessage() { Message = $"[WebSearchShortcut] History: Save failed: {ex}", State = MessageState.Error }); + + return; + } + + ExtensionHost.LogMessage($"[WebSearchShortcut] History: Save succeeded"); + } + + private static void RebuildShortcutQueriesMap() + { + _shortcutQueriesMap.Clear(); + + foreach (var (shortcutName, historyEntries) in _cache.Data) + { + _shortcutQueriesMap[shortcutName] = [ + .. (historyEntries ?? Enumerable.Empty()) + .OrderByDescending(entry => entry.Timestamp) + .Select(entry => entry.Query) + .Distinct(StringComparer.OrdinalIgnoreCase) + ]; + } + } +} diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryStorage.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryStorage.cs new file mode 100644 index 0000000..6bbd475 --- /dev/null +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryStorage.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using WebSearchShortcut.Helpers; + +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace WebSearchShortcut.History; + +internal sealed class HistoryStorage +{ + public Dictionary> Data { get; set; } = []; + + public static HistoryStorage ReadFromFile(string path) + { + if (!File.Exists(path)) + { + HistoryStorage empty = new(); + + WriteToFile(path, empty); + + return empty; + } + + string jsonString; + + try + { + jsonString = File.ReadAllText(path); + } + catch (Exception ex) + { + ExtensionHost.LogMessage($"[HistoryStorage] ReadFile failed: {ex.GetType().Name}: {ex.Message}"); + + throw; + } + + if (string.IsNullOrWhiteSpace(jsonString)) + { + return new HistoryStorage(); + } + + try + { + return JsonSerializer.Deserialize(jsonString, AppJsonSerializerContext.Default.HistoryStorage) ?? new HistoryStorage(); + } + catch (Exception ex) + { + ExtensionHost.LogMessage($"[HistoryStorage] JsonDeserialize failed: {ex.GetType().Name}: {ex.Message}"); + + throw; + } + } + + public static void WriteToFile(string path, HistoryStorage data) + { + var jsonString = JsonPrettyFormatter.ToPrettyJson(data, AppJsonSerializerContext.Default.HistoryStorage); + + try + { + File.WriteAllText(path, jsonString); + } + catch (Exception ex) + { + ExtensionHost.LogMessage($"[HistoryStorage] WriteFile failed: {ex.GetType().Name}: {ex.Message}"); + + throw; + } + } +} diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/JSONContext.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/JSONContext.cs index 63c62f0..c2e40df 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/JSONContext.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/JSONContext.cs @@ -1,10 +1,13 @@ using System.Text.Json.Serialization; +using WebSearchShortcut.History; namespace WebSearchShortcut; [JsonSourceGenerationOptions(IncludeFields = true)] [JsonSerializable(typeof(Storage))] [JsonSerializable(typeof(WebSearchShortcutDataEntry))] +[JsonSerializable(typeof(HistoryStorage))] +[JsonSerializable(typeof(HistoryEntry))] internal partial class AppJsonSerializerContext : JsonSerializerContext { } diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs index 6ed01ab..f09f51e 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs @@ -6,6 +6,7 @@ using Microsoft.CommandPalette.Extensions.Toolkit; using WebSearchShortcut.Commands; using WebSearchShortcut.Helpers; +using WebSearchShortcut.History; using WebSearchShortcut.Properties; using WebSearchShortcut.Services; @@ -13,6 +14,8 @@ namespace WebSearchShortcut; internal sealed partial class SearchWebPage : DynamicListPage { + private const int MaxDisplayCount = 100; + private readonly WebSearchShortcutDataEntry _shortcut; private readonly IListItem _openHomepageListItem; @@ -50,11 +53,20 @@ public SearchWebPage(WebSearchShortcutDataEntry shortcut) Title = StringFormatter.Format(Resources.OpenHomepageItem_TitleTemplate, new() { ["shortcut"] = shortcut.Name }), Icon = Icons.Home }; + } - _items = [_openHomepageListItem]; + public override IListItem[] GetItems() + { + if (_items.Length == 0) + Rebuild(); + + return Volatile.Read(ref _items); } - public override IListItem[] GetItems() => Volatile.Read(ref _items); + public void Rebuild() + { + UpdateSearchText(SearchText, SearchText); + } public override async void UpdateSearchText(string oldSearch, string newSearch) { @@ -90,7 +102,9 @@ public override async void UpdateSearchText(string oldSearch, string newSearch) { UpdateSuggestionItems([], currentEpoch); - RenderItems([_openHomepageListItem], currentEpoch); + var historyItems = BuildHistoryItems(); + + RenderItems([_openHomepageListItem, .. historyItems], currentEpoch); return; } @@ -182,6 +196,29 @@ private ListItem[] BuildPrimaryItems(string searchText) ]; } + private ListItem[] BuildHistoryItems() + { + var historyQueries = HistoryService + .Get(_shortcut.Name) + .Take(MaxDisplayCount - 1); + + return [ + .. historyQueries.Select(historyQuery => new ListItem( + new SearchWebCommand(_shortcut, historyQuery) + { + Name = StringFormatter.Format(Resources.SearchQueryItem_NameTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = historyQuery }), + } + ) + { + Title = StringFormatter.Format(Resources.SearchQueryItem_TitleTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = historyQuery }), + Subtitle = StringFormatter.Format(Resources.SearchQueryItem_SubtitleTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = historyQuery }), + Icon = Icons.History, + TextToSuggest = historyQuery, + MoreCommands = [_openHomepageContextItem] + }) + ]; + } + private async Task FetchSuggestionItemsAsync(string searchText, CancellationToken cancellationToken) { var suggestions = await SuggestionsRegistry diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs index f1a1a14..8e3ec4a 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs @@ -50,4 +50,9 @@ internal static class Icons /// Search icon /// public static IconInfo Search { get; } = new("\uE721"); + + /// + /// History icon + /// + public static IconInfo History { get; } = new("\uE81C"); } From df10bb17b4de1d403d6df83b96a01085cdf8240c Mon Sep 17 00:00:00 2001 From: Tortoise <195262249+Testudinidae@users.noreply.github.com> Date: Wed, 20 Aug 2025 05:50:55 -0700 Subject: [PATCH 5/8] feat: implement deleting a single history entry and purging all history for a specific shortcut --- .../History/HistoryService.cs | 31 +++++++++++++++++++ .../WebSearchShortcut/Pages/SearchWebPage.cs | 25 +++++++++++++-- .../WebSearchShortcut/Properties/Icons.cs | 5 +++ .../Properties/Resources.Designer.cs | 18 +++++++++++ .../Properties/Resources.fr.resx | 6 ++++ .../Properties/Resources.resx | 6 ++++ .../Properties/Resources.zh-CN.resx | 6 ++++ .../Properties/Resources.zh-TW.resx | 6 ++++ .../WebSearchShortcutCommandsProvider.cs | 24 +++++++++++--- 9 files changed, 120 insertions(+), 7 deletions(-) diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs index 4d2da3f..4b19f45 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs @@ -56,6 +56,37 @@ public static void Add(string shortcutName, string query) } } + public static void Remove(string shortcutName, string query) + { + lock (_lock) + { + if (!_cache.Data.TryGetValue(shortcutName, out var entries) || entries is null) + return; + + entries.RemoveAll(entry => string.Equals(entry.Query, query, StringComparison.Ordinal)); + + RebuildShortcutQueriesMap(); + + Save(); + + ExtensionHost.LogMessage($"[WebSearchShortcut] History: Delete Query shortcut=\"{shortcutName}\" query=\"{query}\""); + } + } + + public static void RemoveAll(string shortcutName) + { + lock (_lock) + { + _cache.Data[shortcutName] = []; + + RebuildShortcutQueriesMap(); + + Save(); + + ExtensionHost.LogMessage($"[WebSearchShortcut] History: Claer shortcut=\"{shortcutName}\""); + } + } + public static void Reload() { lock (_lock) diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs index f09f51e..8cdeb60 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs @@ -184,7 +184,8 @@ private ListItem[] BuildPrimaryItems(string searchText) new ListItem( new SearchWebCommand(_shortcut, searchText) { - Name = StringFormatter.Format(Resources.SearchQueryItem_NameTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = searchText }), + Icon = Icons.Search, + Name = StringFormatter.Format(Resources.SearchQueryItem_NameTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = searchText }) } ) { @@ -206,7 +207,8 @@ private ListItem[] BuildHistoryItems() .. historyQueries.Select(historyQuery => new ListItem( new SearchWebCommand(_shortcut, historyQuery) { - Name = StringFormatter.Format(Resources.SearchQueryItem_NameTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = historyQuery }), + Icon = Icons.Search, + Name = StringFormatter.Format(Resources.SearchQueryItem_NameTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = historyQuery }) } ) { @@ -214,7 +216,23 @@ private ListItem[] BuildHistoryItems() Subtitle = StringFormatter.Format(Resources.SearchQueryItem_SubtitleTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = historyQuery }), Icon = Icons.History, TextToSuggest = historyQuery, - MoreCommands = [_openHomepageContextItem] + MoreCommands = [ + _openHomepageContextItem, + new CommandContextItem( + title: StringFormatter.Format(Resources.DeleteHistory_TitleTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = historyQuery }), + name: $"[UNREACHABLE] DeleteHistory.Name - shortcut='{_shortcut.Name}', query='{historyQuery}'", + action: () => + { + HistoryService.Remove(_shortcut.Name, historyQuery); + Rebuild(); + }, + result: CommandResult.KeepOpen() + ) + { + Icon = Icons.DeleteHistory, + IsCritical = true + } + ] }) ]; } @@ -231,6 +249,7 @@ private async Task FetchSuggestionItemsAsync(string searchText, Canc .. suggestions.Select(suggestion => new ListItem( new SearchWebCommand(_shortcut, suggestion.Title) { + Icon = Icons.Search, Name = StringFormatter.Format(Resources.SearchQueryItem_NameTemplate, new() { ["shortcut"] = _shortcut.Name, ["query"] = suggestion.Title }) } ) diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs index 8e3ec4a..f7d9530 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Icons.cs @@ -55,4 +55,9 @@ internal static class Icons /// History icon /// public static IconInfo History { get; } = new("\uE81C"); + + /// + /// Delete History icon + /// + public static readonly IconInfo DeleteHistory = new("\uE894"); } diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs index 6b0e6fc..df841bd 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs @@ -249,6 +249,24 @@ internal static string AddShortcutPage_Title_Edit { } } + /// + /// Looks up a localized string similar to Clear {shortcut}'s History. + /// + internal static string ClearHistory_TitleTemplate { + get { + return ResourceManager.GetString("ClearHistory_TitleTemplate", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Delete. + /// + internal static string DeleteHistory_TitleTemplate { + get { + return ResourceManager.GetString("DeleteHistory_TitleTemplate", resourceCulture); + } + } + /// /// Looks up a localized string similar to Delete. /// diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx index 9794f37..a784c78 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx @@ -192,6 +192,9 @@ {shortcut} + + + Modifier le raccourci de recherche @@ -219,4 +222,7 @@ Rechercher "{query}" + + + \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx index 2473c59..79dc5f1 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx @@ -192,6 +192,9 @@ {shortcut} + + Clear {shortcut}'s History + Edit Search Shortcut @@ -219,4 +222,7 @@ Search for "{query}" + + Delete + \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx index 728e370..edd25b0 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx @@ -192,6 +192,9 @@ {shortcut} + + + 编辑搜索捷径 @@ -219,4 +222,7 @@ 搜索“{query}” + + + \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx index c0f1794..4b0a002 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx @@ -192,6 +192,9 @@ {shortcut} + + 清除 {shortcut} 的歷史紀錄 + 編輯搜尋捷徑 @@ -219,4 +222,7 @@ 搜尋「{query}」 + + 將「{query}」從歷史紀錄刪除 + \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs index 773fe1b..2453894 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs @@ -10,6 +10,7 @@ using Microsoft.CommandPalette.Extensions; using Microsoft.CommandPalette.Extensions.Toolkit; using WebSearchShortcut.Helpers; +using WebSearchShortcut.History; using WebSearchShortcut.Properties; using WebSearchShortcut.Services; @@ -131,6 +132,11 @@ private void LoadShortcutFromFile() private CommandItem CreateCommandItem(WebSearchShortcutDataEntry shortcut) { + var searchWebPage = new SearchWebPage(shortcut) + { + Name = StringFormatter.Format(Resources.ShortcutItem_NameTemplate, new() { ["shortcut"] = shortcut.Name }) + }; + var editShortcutPage = new AddShortcutPage(shortcut) { Name = StringFormatter.Format(Resources.EditShortcutItem_NameTemplate, new() { ["shortcut"] = shortcut.Name }), @@ -163,12 +169,22 @@ private CommandItem CreateCommandItem(WebSearchShortcutDataEntry shortcut) IsCritical = true }; - var commandItem = new CommandItem( - new SearchWebPage(shortcut) + var clearHistoryCommand = new CommandContextItem( + title: StringFormatter.Format(Resources.ClearHistory_TitleTemplate, new() { ["shortcut"] = shortcut.Name }), + name: $"[UNREACHABLE] ClearHistory.Name - shortcut='{shortcut.Name}'", + action: () => { - Name = StringFormatter.Format(Resources.ShortcutItem_NameTemplate, new() { ["shortcut"] = shortcut.Name }) - } + HistoryService.RemoveAll(shortcut.Name); + searchWebPage.Rebuild(); + }, + result: CommandResult.KeepOpen() ) + { + Icon = Icons.DeleteHistory, + IsCritical = true + }; + + var commandItem = new CommandItem(searchWebPage) { Title = StringFormatter.Format(Resources.ShortcutItem_TitleTemplate, new() { ["shortcut"] = shortcut.Name }), Subtitle = StringFormatter.Format(Resources.ShortcutItem_SubtitleTemplate, new() { ["shortcut"] = shortcut.Name }), From f0b16e30205583f2879c3abe9d9a39400da8d5cc Mon Sep 17 00:00:00 2001 From: Tortoise <195262249+Testudinidae@users.noreply.github.com> Date: Mon, 18 Aug 2025 23:49:19 -0700 Subject: [PATCH 6/8] feat: show history and suggestions together --- .../WebSearchShortcut/History/HistoryService.cs | 11 +++++++++++ .../WebSearchShortcut/Pages/SearchWebPage.cs | 15 ++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs index 4b19f45..3ed7fda 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/History/HistoryService.cs @@ -39,6 +39,17 @@ public static string[] Get(string shortcutName) } } + public static string[] Search(string shortcutName, string searchText) + { + lock (_lock) + { + return [ + .. Get(shortcutName) + .Where(query => query.StartsWith(searchText, StringComparison.OrdinalIgnoreCase)) + ]; + } + } + public static void Add(string shortcutName, string query) { lock (_lock) diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs index 8cdeb60..485c431 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs @@ -14,6 +14,7 @@ namespace WebSearchShortcut; internal sealed partial class SearchWebPage : DynamicListPage { + private const int MaxHistoryDisplayCount = 3; private const int MaxDisplayCount = 100; private readonly WebSearchShortcutDataEntry _shortcut; @@ -98,12 +99,12 @@ public override async void UpdateSearchText(string oldSearch, string newSearch) { } + var historyItems = BuildHistoryItems(newSearch); + if (shouldOpenHomePage) { UpdateSuggestionItems([], currentEpoch); - var historyItems = BuildHistoryItems(); - RenderItems([_openHomepageListItem, .. historyItems], currentEpoch); return; @@ -112,7 +113,7 @@ public override async void UpdateSearchText(string oldSearch, string newSearch) var primaryItems = BuildPrimaryItems(newSearch); var snapshotSuggestions = Volatile.Read(ref _suggestionItems); - RenderItems([.. primaryItems, .. snapshotSuggestions], currentEpoch); + RenderItems([.. primaryItems, .. historyItems, .. snapshotSuggestions], currentEpoch); if (!shouldFetchSuggestions) return; @@ -144,7 +145,7 @@ public override async void UpdateSearchText(string oldSearch, string newSearch) UpdateSuggestionItems(suggestionItems, currentEpoch); - RenderItems([.. primaryItems, .. suggestionItems], currentEpoch); + RenderItems([.. primaryItems, .. historyItems, .. suggestionItems], currentEpoch); } private void RenderItems(IListItem[] items, int currentUpdateSearchTextEpoch) @@ -197,11 +198,11 @@ private ListItem[] BuildPrimaryItems(string searchText) ]; } - private ListItem[] BuildHistoryItems() + private ListItem[] BuildHistoryItems(string searchText) { var historyQueries = HistoryService - .Get(_shortcut.Name) - .Take(MaxDisplayCount - 1); + .Search(_shortcut.Name, searchText) + .Take(string.IsNullOrEmpty(searchText) ? MaxDisplayCount : MaxHistoryDisplayCount); return [ .. historyQueries.Select(historyQuery => new ListItem( From 19dd5702b7b524168c4b32ffd3d3b7558ab33e9b Mon Sep 17 00:00:00 2001 From: Tortoise <195262249+Testudinidae@users.noreply.github.com> Date: Tue, 19 Aug 2025 20:45:45 -0700 Subject: [PATCH 7/8] feat: introduce ItemIntegrate to merge primary/history/suggestions --- .../WebSearchShortcut/Pages/SearchWebPage.cs | 77 +++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs index 485c431..f7530dc 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -19,11 +20,11 @@ internal sealed partial class SearchWebPage : DynamicListPage private readonly WebSearchShortcutDataEntry _shortcut; - private readonly IListItem _openHomepageListItem; - private readonly IContextItem _openHomepageContextItem; + private readonly ListItem _openHomepageListItem; + private readonly CommandContextItem _openHomepageContextItem; + private ListItem[] _suggestionItems = []; private IListItem[] _items = []; - private IListItem[] _suggestionItems = []; private int _lastUpdateSearchTextEpoch; private readonly Lock _swapSuggestionsCancellationSourceLock = new(); @@ -113,12 +114,12 @@ public override async void UpdateSearchText(string oldSearch, string newSearch) var primaryItems = BuildPrimaryItems(newSearch); var snapshotSuggestions = Volatile.Read(ref _suggestionItems); - RenderItems([.. primaryItems, .. historyItems, .. snapshotSuggestions], currentEpoch); + RenderItems(ItemIntegrate(primaryItems, historyItems, snapshotSuggestions), currentEpoch); if (!shouldFetchSuggestions) return; - IListItem[] suggestionItems; + ListItem[] suggestionItems; try { suggestionItems = await FetchSuggestionItemsAsync(newSearch, currentCancellationSource!.Token).ConfigureAwait(false); @@ -145,7 +146,69 @@ public override async void UpdateSearchText(string oldSearch, string newSearch) UpdateSuggestionItems(suggestionItems, currentEpoch); - RenderItems([.. primaryItems, .. historyItems, .. suggestionItems], currentEpoch); + RenderItems(ItemIntegrate(primaryItems, historyItems, suggestionItems), currentEpoch); + } + + private static IListItem[] ItemIntegrate(ListItem[] primaryItems, ListItem[] historyItems, ListItem[] suggestionItems) + { + var primaryItemByTitle = primaryItems.ToDictionary(item => item.Title, item => item); + var historyItemByTitle = historyItems.ToDictionary(item => item.Title, item => item); + var suggestionItemByTitle = suggestionItems.ToDictionary(item => item.Title, item => item); + + HashSet removeFromHistory = []; + HashSet removeFromSuggestions = []; + + foreach (var (title, primaryItem) in primaryItemByTitle) + { + if (historyItemByTitle.TryGetValue(title, out var historyItem)) + { + primaryItem.Icon = Icons.History; + primaryItem.MoreCommands = historyItem.MoreCommands; + + if (suggestionItemByTitle.TryGetValue(title, out var suggestionItem)) + { + historyItem.Subtitle = suggestionItem.Subtitle; + removeFromSuggestions.Add(title); + } + else + { + removeFromHistory.Add(title); + } + } + } + + foreach (var (title, historyItem) in historyItemByTitle) + { + if (removeFromHistory.Contains(title)) + continue; + + if (removeFromSuggestions.Contains(title)) + continue; + + if (suggestionItemByTitle.TryGetValue(title, out var suggestionItem)) + { + historyItem.Subtitle = suggestionItem.Subtitle; + removeFromSuggestions.Add(title); + } + } + + List items = new(primaryItems.Length + historyItems.Length + suggestionItems.Length); + + items.AddRange(primaryItems); + + foreach (var historyItem in historyItems) + { + if (!removeFromHistory.Contains(historyItem.Title)) + items.Add(historyItem); + } + + foreach (var suggestoinItem in suggestionItems) + { + if (!removeFromSuggestions.Contains(suggestoinItem.Title)) + items.Add(suggestoinItem); + } + + return [.. items.Take(MaxDisplayCount)]; } private void RenderItems(IListItem[] items, int currentUpdateSearchTextEpoch) @@ -164,7 +227,7 @@ private void RenderItems(IListItem[] items, int currentUpdateSearchTextEpoch) RaiseItemsChanged(items.Length); } - private void UpdateSuggestionItems(IListItem[] suggestionItems, int currentUpdateSearchTextEpoch) + private void UpdateSuggestionItems(ListItem[] suggestionItems, int currentUpdateSearchTextEpoch) { if (currentUpdateSearchTextEpoch != Volatile.Read(ref _lastUpdateSearchTextEpoch)) return; From 75e9e28a63382c6323987ab984f5bb8fee8a6d32 Mon Sep 17 00:00:00 2001 From: Tortoise <195262249+Testudinidae@users.noreply.github.com> Date: Wed, 20 Aug 2025 04:41:25 -0700 Subject: [PATCH 8/8] feat: add settings that let users set a display cap and tune the history-to-suggestions ratio, plus per-shortcut controls to enable or disable saving history --- .../Commands/SearchWebCommand.cs | 9 +-- .../Forms/AddShortcutForm.cs | 14 +++- .../WebSearchShortcut/Helpers/IntSetting.cs | 77 +++++++++++++++++++ .../Helpers/SettingsManager.cs | 63 +++++++++++++++ .../WebSearchShortcut/Pages/SearchWebPage.cs | 17 ++-- .../Properties/Resources.Designer.cs | 72 +++++++++++++++++ .../Properties/Resources.fr.resx | 24 ++++++ .../Properties/Resources.resx | 24 ++++++ .../Properties/Resources.zh-CN.resx | 24 ++++++ .../Properties/Resources.zh-TW.resx | 24 ++++++ .../WebSearchShortcutCommandsProvider.cs | 6 +- .../WebSearchShortcutDataEntry.cs | 1 + 12 files changed, 339 insertions(+), 16 deletions(-) create mode 100644 CmdPalWebSearchShortcut/WebSearchShortcut/Helpers/IntSetting.cs create mode 100644 CmdPalWebSearchShortcut/WebSearchShortcut/Helpers/SettingsManager.cs diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs index a380f77..9e4587a 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Commands/SearchWebCommand.cs @@ -18,7 +18,6 @@ public SearchWebCommand(WebSearchShortcutDataEntry shortcut, string query) _query = query; _shortcut = shortcut; _browserInfo = new BrowserExecutionInfo(shortcut); - // _settingsManager = settingsManager; } public override CommandResult Invoke() @@ -29,12 +28,8 @@ public override CommandResult Invoke() return CommandResult.KeepOpen(); } - HistoryService.Add(_shortcut.Name, _query); - - // if (_settingsManager.ShowHistory != Resources.history_none) - // { - // _settingsManager.SaveHistory(new HistoryItem(Arguments, DateTime.Now)); - // } + if (_shortcut.RecordHistory ?? true) + HistoryService.Add(_shortcut.Name, _query); return CommandResult.Dismiss(); } diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs index f673dd9..7683059 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Forms/AddShortcutForm.cs @@ -1,8 +1,9 @@ +using System; using System.Linq; using System.Text.Json; using System.Text.Json.Nodes; -using Microsoft.CommandPalette.Extensions.Toolkit; using Windows.Foundation; +using Microsoft.CommandPalette.Extensions.Toolkit; using WebSearchShortcut.Browsers; using WebSearchShortcut.Properties; @@ -19,6 +20,7 @@ public AddShortcutForm(WebSearchShortcutDataEntry? shortcut) var url = shortcut?.Url ?? string.Empty; var suggestionProvider = shortcut?.SuggestionProvider ?? string.Empty; var replaceWhitespace = shortcut?.ReplaceWhitespace ?? string.Empty; + var recordHistory = shortcut?.RecordHistory ?? true; var homePage = shortcut?.HomePage ?? string.Empty; var browserPath = shortcut?.BrowserPath ?? string.Empty; var browserArgs = shortcut?.BrowserArgs ?? string.Empty; @@ -68,6 +70,13 @@ public AddShortcutForm(WebSearchShortcutDataEntry? shortcut) "value": {{JsonSerializer.Serialize(suggestionProvider, AppJsonSerializerContext.Default.String)}}, "errorMessage": "// Just for space between items" }, + { + "id": "recordHistory", + "type": "Input.Toggle", + "label": {{JsonSerializer.Serialize(Resources.AddShortcutForm_RecordHistory_Label, AppJsonSerializerContext.Default.String)}}, + "title": {{JsonSerializer.Serialize(Resources.AddShortcutForm_RecordHistory_Title, AppJsonSerializerContext.Default.String)}}, + "value": {{JsonSerializer.Serialize(recordHistory ? "true" : "false", AppJsonSerializerContext.Default.String)}} + }, { "id": "homePage", "type": "Input.Text", @@ -126,6 +135,7 @@ public AddShortcutForm(WebSearchShortcutDataEntry? shortcut) "url": "url", "suggestionProvider": "suggestionProvider", "replaceWhitespace": "replaceWhitespace", + "recordHistory": "recordHistory", "homePage": "homePage", "browserPath": "browserPath", "browserArgs": "browserArgs" @@ -148,11 +158,13 @@ public override CommandResult SubmitForm(string inputs) shortcut.Url = root["url"]?.GetValue() ?? string.Empty; shortcut.SuggestionProvider = root["suggestionProvider"]?.GetValue() ?? string.Empty; shortcut.ReplaceWhitespace = root["replaceWhitespace"]?.GetValue() ?? string.Empty; + shortcut.RecordHistory = string.Equals(root["recordHistory"]?.GetValue() ?? "true", "true", StringComparison.OrdinalIgnoreCase); shortcut.HomePage = root["homePage"]?.GetValue() ?? string.Empty; shortcut.BrowserPath = root["browserPath"]?.GetValue() ?? string.Empty; shortcut.BrowserArgs = root["browserArgs"]?.GetValue() ?? string.Empty; AddedCommand?.Invoke(this, shortcut); + return CommandResult.GoHome(); } } diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Helpers/IntSetting.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Helpers/IntSetting.cs new file mode 100644 index 0000000..87a0f5d --- /dev/null +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Helpers/IntSetting.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Text.Json.Nodes; + +namespace Microsoft.CommandPalette.Extensions.Toolkit; + +public sealed class IntSetting : Setting +{ + public int? Min { get; set; } + public int? Max { get; set; } + public string Placeholder { get; set; } = string.Empty; + + private IntSetting() + : base() + { + Value = 0; + } + + public IntSetting(string key, int defaultValue, int? min = null, int? max = null) + : base(key, defaultValue) + { + Min = min; + Max = max; + } + + public IntSetting(string key, string label, string description, int defaultValue, + int? min = null, int? max = null) + : base(key, label, description, defaultValue) + { + Min = min; + Max = max; + } + + public override Dictionary ToDictionary() + { + var dict = new Dictionary + { + { "id", Key }, + { "type", "Input.Number" }, + { "title", Label }, + { "label", Description }, + { "value", Value }, + { "isRequired", IsRequired }, + { "errorMessage", ErrorMessage }, + { "placeholder", Placeholder }, + }; + + if (Min.HasValue) dict["min"] = Min.Value; + if (Max.HasValue) dict["max"] = Max.Value; + + return dict; + } + + public static IntSetting LoadFromJson(JsonObject jsonObject) => new() { Value = jsonObject["value"]?.GetValue() ?? 0 }; + + public override void Update(JsonObject payload) + { + if (payload.TryGetPropertyValue(Key, out JsonNode? node) && node is not null) + { + if (node is JsonValue jsonValue && jsonValue.TryGetValue(out var value)) + { + Value = value; + } + else if (int.TryParse(node.ToString(), out value)) + { + Value = value; + } + } + + if (Min.HasValue && Value < Min.Value) Value = Min.Value; + if (Max.HasValue && Value > Max.Value) Value = Max.Value; + } + + public override string ToState() + { + return $"\"{Key}\": {Value}"; + } +} diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Helpers/SettingsManager.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Helpers/SettingsManager.cs new file mode 100644 index 0000000..28ff9ed --- /dev/null +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Helpers/SettingsManager.cs @@ -0,0 +1,63 @@ +using System.IO; +using Windows.Foundation; +using Microsoft.CommandPalette.Extensions.Toolkit; +using WebSearchShortcut.Properties; + +namespace WebSearchShortcut.Helpers; + +internal class SettingsManager : JsonSettingsManager +{ + private const string _namespace = "WebSearchShortcut"; + private static string Namespaced(string propertyName) => $"{_namespace}.{propertyName}"; + + private const int _defaultMaxDisplayCount = 20; + private const int _defaultMaxHistoryDisplayCount = 3; + + private readonly IntSetting _maxDisplayCount = new( + Namespaced(nameof(MaxDisplayCount)), + Resources.Settings_MaxDisplayCount_Label, + Resources.Settings_MaxDisplayCount_Description, + _defaultMaxDisplayCount, + 1, null + ) + { ErrorMessage = Resources.Settings_MaxDisplayCount_ErrorMessage }; + + private readonly IntSetting _maxHistoryDisplayCount = new( + Namespaced(nameof(MaxHistoryDisplayCount)), + Resources.Settings_MaxHistoryDisplayCount_Label, + Resources.Settings_MaxHistoryDisplayCount_Description, + _defaultMaxHistoryDisplayCount, + 0, null + ) + { ErrorMessage = Resources.Settings_MaxHistoryDisplayCount_ErrorMessage }; + + public int MaxDisplayCount => _maxDisplayCount.Value; + public int MaxHistoryDisplayCount => _maxHistoryDisplayCount.Value; + + public event TypedEventHandler? SettingsChanged + { + add => Settings.SettingsChanged += value; + remove => Settings.SettingsChanged -= value; + } + + internal static string SettingsJsonPath() + { + var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal"); + + Directory.CreateDirectory(directory); + + return Path.Combine(directory, "settings.json"); + } + + public SettingsManager() + { + FilePath = SettingsJsonPath(); + + Settings.Add(_maxDisplayCount); + Settings.Add(_maxHistoryDisplayCount); + + LoadSettings(); + + Settings.SettingsChanged += (s, a) => SaveSettings(); + } +} diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs index f7530dc..8cbe1bc 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Pages/SearchWebPage.cs @@ -15,9 +15,6 @@ namespace WebSearchShortcut; internal sealed partial class SearchWebPage : DynamicListPage { - private const int MaxHistoryDisplayCount = 3; - private const int MaxDisplayCount = 100; - private readonly WebSearchShortcutDataEntry _shortcut; private readonly ListItem _openHomepageListItem; @@ -26,13 +23,15 @@ internal sealed partial class SearchWebPage : DynamicListPage private ListItem[] _suggestionItems = []; private IListItem[] _items = []; + private readonly SettingsManager _settingsManager; + private int _lastUpdateSearchTextEpoch; private readonly Lock _swapSuggestionsCancellationSourceLock = new(); private readonly Lock _renderLock = new(); private readonly Lock _updateSuggestionLock = new(); private CancellationTokenSource? _previousSuggestionsCancellationSource; - public SearchWebPage(WebSearchShortcutDataEntry shortcut) + public SearchWebPage(WebSearchShortcutDataEntry shortcut, SettingsManager settingsManager) { Id = shortcut.Id; Title = StringFormatter.Format(Resources.SearchWebPage_TitleTemplate, new() { ["shortcut"] = shortcut.Name }); @@ -55,6 +54,10 @@ public SearchWebPage(WebSearchShortcutDataEntry shortcut) Title = StringFormatter.Format(Resources.OpenHomepageItem_TitleTemplate, new() { ["shortcut"] = shortcut.Name }), Icon = Icons.Home }; + + _settingsManager = settingsManager; + + _settingsManager.SettingsChanged += (s, a) => Rebuild(); } public override IListItem[] GetItems() @@ -149,7 +152,7 @@ public override async void UpdateSearchText(string oldSearch, string newSearch) RenderItems(ItemIntegrate(primaryItems, historyItems, suggestionItems), currentEpoch); } - private static IListItem[] ItemIntegrate(ListItem[] primaryItems, ListItem[] historyItems, ListItem[] suggestionItems) + private IListItem[] ItemIntegrate(ListItem[] primaryItems, ListItem[] historyItems, ListItem[] suggestionItems) { var primaryItemByTitle = primaryItems.ToDictionary(item => item.Title, item => item); var historyItemByTitle = historyItems.ToDictionary(item => item.Title, item => item); @@ -208,7 +211,7 @@ private static IListItem[] ItemIntegrate(ListItem[] primaryItems, ListItem[] his items.Add(suggestoinItem); } - return [.. items.Take(MaxDisplayCount)]; + return [.. items.Take(_settingsManager.MaxDisplayCount)]; } private void RenderItems(IListItem[] items, int currentUpdateSearchTextEpoch) @@ -265,7 +268,7 @@ private ListItem[] BuildHistoryItems(string searchText) { var historyQueries = HistoryService .Search(_shortcut.Name, searchText) - .Take(string.IsNullOrEmpty(searchText) ? MaxDisplayCount : MaxHistoryDisplayCount); + .Take(string.IsNullOrEmpty(searchText) ? _settingsManager.MaxDisplayCount : _settingsManager.MaxHistoryDisplayCount); return [ .. historyQueries.Select(historyQuery => new ListItem( diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs index df841bd..1901ada 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.Designer.cs @@ -132,6 +132,24 @@ internal static string AddShortcutForm_Name_Label { } } + /// + /// Looks up a localized string similar to Search History. + /// + internal static string AddShortcutForm_RecordHistory_Label { + get { + return ResourceManager.GetString("AddShortcutForm_RecordHistory_Label", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Record history for this shortcut. + /// + internal static string AddShortcutForm_RecordHistory_Title { + get { + return ResourceManager.GetString("AddShortcutForm_RecordHistory_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Replace Whitespace (Optional). /// @@ -348,6 +366,60 @@ internal static string SearchWebPage_TitleTemplate { } } + /// + /// Looks up a localized string similar to Maximum total items shown per search. + /// + internal static string Settings_MaxDisplayCount_Description { + get { + return ResourceManager.GetString("Settings_MaxDisplayCount_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Must be an integer greater than or equal to 1. + /// + internal static string Settings_MaxDisplayCount_ErrorMessage { + get { + return ResourceManager.GetString("Settings_MaxDisplayCount_ErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Max Search Results. + /// + internal static string Settings_MaxDisplayCount_Label { + get { + return ResourceManager.GetString("Settings_MaxDisplayCount_Label", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Maximum number of history results shown during search; set to 0 to hide history. Any value exceeding “Max Search Results” is ignored.. + /// + internal static string Settings_MaxHistoryDisplayCount_Description { + get { + return ResourceManager.GetString("Settings_MaxHistoryDisplayCount_Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Must be an integer greater than or equal to 0. + /// + internal static string Settings_MaxHistoryDisplayCount_ErrorMessage { + get { + return ResourceManager.GetString("Settings_MaxHistoryDisplayCount_ErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Max History Results. + /// + internal static string Settings_MaxHistoryDisplayCount_Label { + get { + return ResourceManager.GetString("Settings_MaxHistoryDisplayCount_Label", resourceCulture); + } + } + /// /// Looks up a localized string similar to {shortcut}. /// diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx index a784c78..84cfdad 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.fr.resx @@ -156,6 +156,12 @@ Aucun + + + + + + Page d'accueil personnalisée (Optionnel) @@ -225,4 +231,22 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx index 79dc5f1..6302efd 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.resx @@ -156,6 +156,12 @@ None + + Search History + + + Record history for this shortcut + Custom Home Page (Optional) @@ -225,4 +231,22 @@ Delete + + Max Search Results + + + Maximum total items shown per search + + + Must be an integer greater than or equal to 1 + + + Max History Results + + + Maximum number of history results shown during search; set to 0 to hide history. Any value exceeding “Max Search Results” is ignored. + + + Must be an integer greater than or equal to 0 + \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx index edd25b0..2c61ebf 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-CN.resx @@ -156,6 +156,12 @@ + + + + + + 自定义主页(可选) @@ -225,4 +231,22 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx index 4b0a002..5d49a2e 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/Properties/Resources.zh-TW.resx @@ -156,6 +156,12 @@ + + 搜尋歷史 + + + 記錄此捷徑的搜尋歷史 + 自訂首頁(可選) @@ -225,4 +231,22 @@ 將「{query}」從歷史紀錄刪除 + + 搜尋結果顯示上限 + + + 一次搜尋最多顯示的總項目數 + + + 必須是大於或等於 1 的整數 + + + 搜尋歷史顯示上限 + + + 搜尋時顯示的歷史結果上限;設為 0 不顯示歷史。超過「搜尋結果顯示上限」的部分會被忽略 + + + 必須是大於或等於 0 的整數 + \ No newline at end of file diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs index 2453894..dc30845 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutCommandsProvider.cs @@ -18,14 +18,18 @@ namespace WebSearchShortcut; public partial class WebSearchShortcutCommandsProvider : CommandProvider { + private static readonly SettingsManager _settingsManager = new(); + private readonly ICommandItem _addShortcutItem; private ICommandItem[] _topLevelCommands = []; + private Storage? _storage; public WebSearchShortcutCommandsProvider() { DisplayName = Resources.WebSearchShortcut_DisplayName; Icon = Icons.Logo; + Settings = _settingsManager.Settings; var addShortcutPage = new AddShortcutPage(null) { @@ -132,7 +136,7 @@ private void LoadShortcutFromFile() private CommandItem CreateCommandItem(WebSearchShortcutDataEntry shortcut) { - var searchWebPage = new SearchWebPage(shortcut) + var searchWebPage = new SearchWebPage(shortcut, _settingsManager) { Name = StringFormatter.Format(Resources.ShortcutItem_NameTemplate, new() { ["shortcut"] = shortcut.Name }) }; diff --git a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutDataEntry.cs b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutDataEntry.cs index bcc784b..4c32968 100644 --- a/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutDataEntry.cs +++ b/CmdPalWebSearchShortcut/WebSearchShortcut/WebSearchShortcutDataEntry.cs @@ -12,6 +12,7 @@ internal sealed class WebSearchShortcutDataEntry public string Url { get; set; } = string.Empty; // public string[]? Urls { get; set; } public string? SuggestionProvider { get; set; } + public bool? RecordHistory { get; set; } public string? HomePage { get; set; } public string? IconUrl { get; set; } public string? ReplaceWhitespace { get; set; }