From 2e1e21c5eb00a49bc9b4bacf187b94c141218b83 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sun, 13 Jul 2025 16:42:13 +0100 Subject: [PATCH 01/29] Update dependencies --- .../Flow.Launcher.Plugin.OneNote.csproj | 5 +++-- Flow.Launcher.Plugin.OneNote/plugin.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj b/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj index ffdf7d0..96cf22c 100644 --- a/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj +++ b/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj @@ -10,6 +10,7 @@ flow-launcher flow-plugin false true + enable true true en @@ -27,10 +28,10 @@ - + - + diff --git a/Flow.Launcher.Plugin.OneNote/plugin.json b/Flow.Launcher.Plugin.OneNote/plugin.json index 5abc2eb..d1bc17e 100644 --- a/Flow.Launcher.Plugin.OneNote/plugin.json +++ b/Flow.Launcher.Plugin.OneNote/plugin.json @@ -4,7 +4,7 @@ "Name": "OneNote", "Description": "Search and create your OneNote notes", "Author": "Odotocodot", - "Version": "2.1.2", + "Version": "2.2.0-beta", "Language": "csharp", "Website": "https://github.com/Odotocodot/Flow.Launcher.Plugin.OneNote", "IcoPath": "Images/logo.png", From 9da1f5fcec9030b83925ea0a295c65675588c1b3 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sun, 20 Jul 2025 20:07:58 +0100 Subject: [PATCH 02/29] Use new API --- .../Icons/IconGeneratorInfo.cs | 34 ++++---- Flow.Launcher.Plugin.OneNote/Main.cs | 19 +++-- Flow.Launcher.Plugin.OneNote/ResultCreator.cs | 80 ++++++++----------- .../SearchManager.NotebookExplorer.cs | 49 ++++++++---- Flow.Launcher.Plugin.OneNote/WindowHelper.cs | 3 +- 5 files changed, 97 insertions(+), 88 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs b/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs index dfe0df2..57214e4 100644 --- a/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs +++ b/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs @@ -8,23 +8,25 @@ public record struct IconGeneratorInfo public string Prefix { get; } public Color? Color { get; } - public IconGeneratorInfo(OneNoteNotebook notebook) + public IconGeneratorInfo(IOneNoteItem item) { - Prefix = IconConstants.Notebook; - Color = notebook.Color; - } - public IconGeneratorInfo(OneNoteSectionGroup sectionGroup) - { - Prefix = sectionGroup.IsRecycleBin ? IconConstants.RecycleBin : IconConstants.SectionGroup; - } - public IconGeneratorInfo(OneNoteSection section) - { - Prefix = IconConstants.Section; - Color = section.Color; - } - public IconGeneratorInfo(OneNotePage page) - { - Prefix = IconConstants.Page; + switch (item) + { + case OneNoteNotebook n: + Prefix = IconConstants.Notebook; + Color = n.Color; + break; + case OneNoteSectionGroup sg: + Prefix = sg.IsRecycleBin ? IconConstants.RecycleBin : IconConstants.SectionGroup; + break; + case OneNoteSection s: + Prefix = IconConstants.Section; + Color = s.Color; + break; + case OneNotePage: + Prefix = IconConstants.Page; + break; + } } } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Main.cs b/Flow.Launcher.Plugin.OneNote/Main.cs index 4bdb0b8..e17deb9 100644 --- a/Flow.Launcher.Plugin.OneNote/Main.cs +++ b/Flow.Launcher.Plugin.OneNote/Main.cs @@ -8,6 +8,7 @@ using Odotocodot.OneNote.Linq; namespace Flow.Launcher.Plugin.OneNote { + #nullable disable public class Main : IAsyncPlugin, IContextMenu, ISettingProvider, IDisposable { private PluginInitContext context; @@ -21,10 +22,12 @@ public class Main : IAsyncPlugin, IContextMenu, ISettingProvider, IDisposable private static Main instance; private Query currentQuery; + public Task InitAsync(PluginInitContext context) { this.context = context; settings = context.API.LoadSettingJsonStorage(); + iconProvider = new IconProvider(context, settings); resultCreator = new ResultCreator(context, settings, iconProvider); searchManager = new SearchManager(context, settings, resultCreator); @@ -34,27 +37,28 @@ public Task InitAsync(PluginInitContext context) return Task.CompletedTask; } - public void OnVisibilityChanged(object _, VisibilityChangedEventArgs e) + private void OnVisibilityChanged(object _, VisibilityChangedEventArgs e) { if (context.CurrentPluginMetadata.Disabled || !e.IsVisible) { - OneNoteApplication.ReleaseComObject(); + Task.Run(OneNoteApplication.ReleaseComObject); } } private static async Task OneNoteInitAsync(CancellationToken token = default) { - if (semaphore.CurrentCount == 0 || OneNoteApplication.HasComObject) + if (OneNoteApplication.HasComObject) return; - - await semaphore.WaitAsync(token); - OneNoteApplication.InitComObject(); + + if (await semaphore.WaitAsync(0,token)) + OneNoteApplication.InitComObject(); + semaphore.Release(); } public async Task> QueryAsync(Query query, CancellationToken token) { currentQuery = query; - var init = OneNoteInitAsync(token); + Task init = OneNoteInitAsync(token); if (string.IsNullOrEmpty(query.Search)) return resultCreator.EmptyQuery(); @@ -64,6 +68,7 @@ public async Task> QueryAsync(Query query, CancellationToken token) return searchManager.Query(query); } + [Obsolete("Use PluginInitContext.API.ReQuery")] public static void ForceReQuery() => instance.context.API.ChangeQuery(instance.currentQuery.RawQuery, true); public List LoadContextMenus(Result selectedResult) diff --git a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs index 4824b43..6e90240 100644 --- a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs +++ b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs @@ -7,6 +7,7 @@ using Flow.Launcher.Plugin.OneNote.UI.Views; using Humanizer; using Odotocodot.OneNote.Linq; +using Odotocodot.OneNote.Linq.Abstractions; namespace Flow.Launcher.Plugin.OneNote { @@ -19,7 +20,6 @@ public class ResultCreator private const string PathSeparator = " > "; private const string BulletPoint = "\u2022 "; private const string TrianglePoint = "\u2023 "; - private string ActionKeyword => context.CurrentPluginMetadata.ActionKeyword; public ResultCreator(PluginInitContext context, Settings settings, IconProvider iconProvider) { @@ -31,7 +31,7 @@ public ResultCreator(PluginInitContext context, Settings settings, IconProvider private static string GetNicePath(IOneNoteItem item, string separator = PathSeparator) => item.RelativePath.Replace(OneNoteApplication.RelativePathSeparator.ToString(), separator); - private string GetTitle(IOneNoteItem item, List highlightData) + private string GetTitle(IOneNoteItem item, List? highlightData) { string title = item.Name; if (!item.IsUnread || !settings.ShowUnread) @@ -51,8 +51,7 @@ private string GetTitle(IOneNoteItem item, List highlightData) private string GetAutoCompleteText(IOneNoteItem item) => $"{ActionKeyword} {settings.Keywords.NotebookExplorer}{GetNicePath(item, Keywords.NotebookExplorerSeparator)}{Keywords.NotebookExplorerSeparator}"; - - + public List EmptyQuery() { return new List @@ -63,7 +62,8 @@ public List EmptyQuery() SubTitle = "Try typing something!", AutoCompleteText = ActionKeyword, IcoPath = iconProvider.Search, - Score = 5000, + AddSelectedCount = false, + Score = Result.MaxScore, }, new Result { @@ -71,6 +71,7 @@ public List EmptyQuery() SubTitle = $"Type \"{settings.Keywords.NotebookExplorer}\" or select this option to search by notebook structure", AutoCompleteText = $"{ActionKeyword} {settings.Keywords.NotebookExplorer}", IcoPath = iconProvider.NotebookExplorer, + AddSelectedCount = false, Score = 2000, Action = _ => { @@ -84,6 +85,7 @@ public List EmptyQuery() SubTitle = $"Type \"{settings.Keywords.RecentPages}\" or select this option to see recently modified pages", AutoCompleteText = $"{ActionKeyword} {settings.Keywords.RecentPages}", IcoPath = iconProvider.Recent, + AddSelectedCount = false, Score = -1000, Action = _ => { @@ -95,6 +97,7 @@ public List EmptyQuery() { Title = "New quick note", IcoPath = iconProvider.QuickNote, + AddSelectedCount = false, Score = -4000, PreviewPanel = GetNewPagePreviewPanel(null, null), Action = _ => @@ -108,6 +111,7 @@ public List EmptyQuery() { Title = "Open and sync notebooks", IcoPath = iconProvider.Sync, + AddSelectedCount = false, Score = int.MinValue, Action = _ => { @@ -130,45 +134,32 @@ public List EmptyQuery() }; } - public Result CreateOneNoteItemResult(IOneNoteItem item, bool actionIsAutoComplete, List highlightData = null, int score = 0) + public Result CreateOneNoteItemResult(IOneNoteItem item, bool actionIsAutoComplete, List? highlightData = null, int score = 0) { - string title = GetTitle(item, highlightData); - string toolTip = string.Empty; - string subTitle = GetNicePath(item); - string autoCompleteText = GetAutoCompleteText(item); - - IconGeneratorInfo iconInfo; + var title = GetTitle(item, highlightData); + var toolTip = string.Empty; + var subTitle = GetNicePath(item); + var autoCompleteText = GetAutoCompleteText(item); + var iconInfo = new IconGeneratorInfo(item); switch (item) { - case OneNoteNotebook notebook: + case INotebookOrSectionGroup i: toolTip = $""" Last Modified: - {TrianglePoint}{notebook.LastModified:F} + {TrianglePoint}{i.LastModified:F} Contains: - {TrianglePoint}{"section group".ToQuantity(notebook.SectionGroups.Count())} - {TrianglePoint}{"section".ToQuantity(notebook.Sections.Count())} - {TrianglePoint}{"page".ToQuantity(notebook.GetPages().Count())} + {TrianglePoint}{"section group".ToQuantity(i.SectionGroups.Count())} + {TrianglePoint}{"section".ToQuantity(i.Sections.Count())} + {TrianglePoint}{"page".ToQuantity(i.GetPages().Count())} """; - subTitle = string.Empty; - iconInfo = new IconGeneratorInfo(notebook); - break; - case OneNoteSectionGroup sectionGroup: - toolTip = - $""" - Last Modified: - {TrianglePoint}{sectionGroup.LastModified:F} - - Contains: - {TrianglePoint}{"section group".ToQuantity(sectionGroup.SectionGroups.Count())} - {TrianglePoint}{"section".ToQuantity(sectionGroup.Sections.Count())} - {TrianglePoint}{"page".ToQuantity(sectionGroup.GetPages().Count())} - """; - - iconInfo = new IconGeneratorInfo(sectionGroup); + if (i is OneNoteNotebook) + { + subTitle = string.Empty; + } break; case OneNoteSection section: if (section.Encrypted) @@ -184,8 +175,6 @@ public Result CreateOneNoteItemResult(IOneNoteItem item, bool actionIsAutoComple Contains: {TrianglePoint}{"page".ToQuantity(section.GetPages().Count())} """; - - iconInfo = new IconGeneratorInfo(section); break; case OneNotePage page: autoCompleteText = actionIsAutoComplete ? autoCompleteText[..^1] : string.Empty; @@ -198,10 +187,6 @@ public Result CreateOneNoteItemResult(IOneNoteItem item, bool actionIsAutoComple {"Created:",-15} {page.Created:F} {"Last Modified:",-15} {page.LastModified:F} """; - iconInfo = new IconGeneratorInfo(page); - break; - default: - iconInfo = default; break; } @@ -222,7 +207,7 @@ public Result CreateOneNoteItemResult(IOneNoteItem item, bool actionIsAutoComple context.API.ChangeQuery($"{autoCompleteText}", true); return false; } - + await Task.Run(() => { item.Sync(); @@ -426,13 +411,12 @@ public List ContextMenu(Result selectedResult) return results; } - public List NoItemsInCollection(List results, IOneNoteItem parent) + public List NoItemsInCollection(List results, IOneNoteItem? parent) { // parent can be null if the collection only contains notebooks. switch (parent) { - case OneNoteNotebook: - case OneNoteSectionGroup: + case INotebookOrSectionGroup: // Can create section/section group results.Add(NoItemsInCollectionResult("section", iconProvider.NewSection, "(unencrypted) section")); results.Add(NoItemsInCollectionResult("section group", iconProvider.NewSectionGroup)); @@ -448,7 +432,7 @@ public List NoItemsInCollection(List results, IOneNoteItem paren return results; - Result NoItemsInCollectionResult(string title, string iconPath, string subTitle = null, OneNoteSection section = null) + Result NoItemsInCollectionResult(string title, string iconPath, string? subTitle = null, OneNoteSection? section = null) { return new Result { @@ -460,7 +444,7 @@ Result NoItemsInCollectionResult(string title, string iconPath, string subTitle } } - private Lazy GetNewPagePreviewPanel(OneNoteSection section, string pageTitle) => + private Lazy GetNewPagePreviewPanel(OneNoteSection? section, string? pageTitle) => new(() => new NewOneNotePagePreviewPanel(context, section, pageTitle)); public static List NoMatchesFound() @@ -469,10 +453,12 @@ public static List NoMatchesFound() "Try searching something else, or syncing your notebooks", IconProvider.Logo); } - public List InvalidQuery() + public List InvalidQuery(bool includeSubtitle = true) { return SingleResult("Invalid query", - "The first character of the search must be a letter or a digit", + includeSubtitle + ? "The first character of the search must be a letter or a digit" + : string.Empty, iconProvider.Warning); } public List SearchingByTitle() diff --git a/Flow.Launcher.Plugin.OneNote/SearchManager.NotebookExplorer.cs b/Flow.Launcher.Plugin.OneNote/SearchManager.NotebookExplorer.cs index c21b239..764cc51 100644 --- a/Flow.Launcher.Plugin.OneNote/SearchManager.NotebookExplorer.cs +++ b/Flow.Launcher.Plugin.OneNote/SearchManager.NotebookExplorer.cs @@ -25,26 +25,45 @@ internal List Query(Query query) string fullSearch = query.Search[(query.Search.IndexOf(Keywords.NotebookExplorer, StringComparison.Ordinal) + Keywords.NotebookExplorer.Length)..]; - IOneNoteItem parent = null; + IOneNoteItem? parent = null; IEnumerable collection = OneNoteApplication.GetNotebooks(); + // // Validate Search Version 1 string[] searches = fullSearch.Split(Keywords.NotebookExplorerSeparator, StringSplitOptions.None); - - for (int i = -1; i < searches.Length - 1; i++) + // for (int i = -1; i < searches.Length - 1; i++) + // { + // if (i < 0) + // { + // continue; + // } + // + // parent = collection.FirstOrDefault(item => item.Name.Equals(searches[i])); + // if (parent == null) + // { + // return results; + // } + // + // collection = parent.Children; + // } + + // Validate Search Version 2 + var separator = Keywords.NotebookExplorerSeparator; + var currIndex = fullSearch.IndexOf(separator, StringComparison.Ordinal); + var prevIndex = 0; + while (currIndex != -1) { - if (i < 0) - { - continue; - } - - parent = collection.FirstOrDefault(item => item.Name.Equals(searches[i])); + //var itemName = fullSearch[prevIndex..currIndex]; + + parent = collection.FirstOrDefault(item => item.Name == fullSearch[prevIndex..currIndex]); if (parent == null) - { - return results; - } - + return resultCreator.InvalidQuery(false); + collection = parent.Children; + + prevIndex = currIndex + 1; + currIndex = fullSearch.IndexOf(separator, currIndex + separator.Length, StringComparison.Ordinal); } + string lastSearch = searches[^1]; @@ -140,14 +159,10 @@ private List Explorer(string search, IOneNoteItem? parent, IEnumerable results) { if (results.Any(result => string.Equals(newItemName.Trim(), result.Title, StringComparison.OrdinalIgnoreCase))) - { return; - } if (parent?.IsInRecycleBin() == true) - { return; - } switch (parent) { diff --git a/Flow.Launcher.Plugin.OneNote/WindowHelper.cs b/Flow.Launcher.Plugin.OneNote/WindowHelper.cs index 54c8ab5..d186520 100644 --- a/Flow.Launcher.Plugin.OneNote/WindowHelper.cs +++ b/Flow.Launcher.Plugin.OneNote/WindowHelper.cs @@ -21,7 +21,8 @@ public static void FocusOneNote() SetForegroundWindow(handle); } - const int SW_RESTORE = 9; + + private const int SW_RESTORE = 9; [LibraryImport("User32.dll")] [return: MarshalAs(UnmanagedType.Bool)] From eddd738d919ed5086843f4b611bb02d28a168702 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sun, 20 Jul 2025 20:09:01 +0100 Subject: [PATCH 03/29] Refactor search manager --- .../Search/DefaultSearch.cs | 22 +++ .../Search/NotebookExplorer.cs | 136 ++++++++++++++++++ .../Search/RecentPages.cs | 25 ++++ .../Search/SearchBase.cs | 16 +++ .../Search/SearchExtensions.cs | 49 +++++++ .../Search/SearchManager.cs | 43 ++++++ .../Search/TitleSearch.cs | 42 ++++++ 7 files changed, 333 insertions(+) create mode 100644 Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs create mode 100644 Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs create mode 100644 Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs create mode 100644 Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs create mode 100644 Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs create mode 100644 Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs create mode 100644 Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs diff --git a/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs b/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs new file mode 100644 index 0000000..669e90a --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using System.Linq; +using Odotocodot.OneNote.Linq; + +namespace Flow.Launcher.Plugin.OneNote.Search +{ + public class DefaultSearch : SearchBase + { + public override List GetResults(string query) + { + if (!char.IsLetterOrDigit(query[0])) + { + return resultCreator.InvalidQuery(); + } + + return OneNoteApplication.FindPages(query) + .Select(pg => resultCreator.CreatePageResult(pg, query)) + .ToList(); + + } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs new file mode 100644 index 0000000..1ec2de0 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Odotocodot.OneNote.Linq; +using Odotocodot.OneNote.Linq.Abstractions; + +namespace Flow.Launcher.Plugin.OneNote.Search +{ + public class NotebookExplorer : SearchBase + { + public override List GetResults(string query) + { + if (ValidateSearch(query, out string? search, out IOneNoteItem? parent, out IEnumerable collection)) + return resultCreator.InvalidQuery(false); + + List results = search switch + { + // Empty search so show all in collection + string when string.IsNullOrWhiteSpace(search) => ShowAll(parent, collection), + + // Search by title + not null when search.StartsWith(Keywords.TitleSearch) && parent is not OneNotePage + => TitleSearch.Filter(search, parent, collection, context, settings, resultCreator), + + // Scoped search + not null when search.StartsWith(Keywords.ScopedSearch) && parent is INotebookOrSectionGroup => ScopedSearch(search, parent), + + // Default search + _ => Explorer(search, parent, collection), + }; + + if (parent == null) + return results; + + Result result = resultCreator.CreateOneNoteItemResult(parent, false, score: Result.MaxScore); + result.Title = $"Open \"{parent.Name}\" in OneNote"; + result.SubTitle = search switch + { + not null when search.StartsWith(Keywords.TitleSearch) => $"Now searching by title in \"{parent.Name}\"", + not null when search.StartsWith(Keywords.ScopedSearch) => $"Now searching all pages in \"{parent.Name}\"", + _ => $"Use \'{Keywords.ScopedSearch}\' to search this item. Use \'{Keywords.TitleSearch}\' to search by title in this item", + }; + + results.Add(result); + return results; + } + + private bool ValidateSearch(string query, out string? lastSearch, out IOneNoteItem? parent, out IEnumerable collection) + { + lastSearch = null; + parent = null; + collection = OneNoteApplication.GetNotebooks(); + + string search = query[(query.IndexOf(Keywords.NotebookExplorer, StringComparison.Ordinal) + Keywords.NotebookExplorer.Length)..]; + + const string separator = Keywords.NotebookExplorerSeparator; + var currIndex = search.IndexOf(separator, StringComparison.Ordinal); + var prevIndex = 0; + + while (currIndex != -1) + { + var itemName = search[prevIndex..currIndex]; + parent = collection.FirstOrDefault(item => item.Name == itemName); + if (parent == null) + return false; + + collection = parent.Children; + + prevIndex = currIndex + 1; + currIndex = search.IndexOf(separator, currIndex + separator.Length, StringComparison.Ordinal); + } + + lastSearch = search[prevIndex..]; + return true; + } + + private List ShowAll(IOneNoteItem? parent, IEnumerable collection) + { + var results = collection.FilterBySettings(settings) + .Select(item => resultCreator.CreateOneNoteItemResult(item, true)) + .ToList(); + + return results.Any() ? results : resultCreator.NoItemsInCollection(results, parent); + } + + private List ScopedSearch(string query, IOneNoteItem parent) + { + if (query.Length == Keywords.ScopedSearch.Length) + return new List(0); + + if (!char.IsLetterOrDigit(query[Keywords.ScopedSearch.Length])) + return resultCreator.InvalidQuery(); + + string currentSearch = query[Keywords.TitleSearch.Length..]; + + return OneNoteApplication.FindPages(currentSearch, parent) + .Select(pg => resultCreator.CreatePageResult(pg, currentSearch)) + .ToList(); + } + + private List Explorer(string search, IOneNoteItem? parent, IEnumerable collection) + { + var results = collection.FilterBySettings(settings) + .FuzzySearch(search, context) + .Select(r => resultCreator.CreateOneNoteItemResult(r.item, true, r.highlightData, r.score)) + .ToList(); + + // If parent is a section, pages inside can have the same name + if (parent is not OneNoteSection && results.Any(result => string.Equals(search.Trim(), result.Title, StringComparison.OrdinalIgnoreCase))) + return results; + + if (parent?.IsInRecycleBin() == true) + return results; + + //Add option to create new items + switch (parent) + { + case null: + results.Add(resultCreator.CreateNewNotebookResult(search)); + break; + case INotebookOrSectionGroup: + results.Add(resultCreator.CreateNewSectionResult(search, parent)); + results.Add(resultCreator.CreateNewSectionGroupResult(search, parent)); + break; + case OneNoteSection section: + if (!section.Locked) + { + results.Add(resultCreator.CreateNewPageResult(search, section)); + } + break; + } + + return results; + } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs b/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs new file mode 100644 index 0000000..1f4e4a1 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using Odotocodot.OneNote.Linq; + +namespace Flow.Launcher.Plugin.OneNote.Search +{ + public class RecentPages : SearchBase + { + public override List GetResults(string query) + { + int count = settings.DefaultRecentsCount; + + if (query.Length > Keyword.Length && int.TryParse(query[Keyword.Length..], out int userChosenCount)) + count = userChosenCount; + + return OneNoteApplication.GetNotebooks() + .GetPages() + .FilterBySettings(settings) + .OrderByDescending(pg => pg.LastModified) + .Take(count) + .Select(resultCreator.CreateRecentPageResult) + .ToList(); + } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs new file mode 100644 index 0000000..df96ac4 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; + +namespace Flow.Launcher.Plugin.OneNote.Search +{ + public abstract class SearchBase + { + protected readonly PluginInitContext context; + protected readonly Settings settings; + protected readonly ResultCreator resultCreator; + public Func KeywordGetter { get; init; } + public string Keyword => KeywordGetter(); + protected Keywords Keywords => settings.Keywords; + public abstract List GetResults(string query); + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs new file mode 100644 index 0000000..53a7806 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using Flow.Launcher.Plugin.SharedModels; +using Odotocodot.OneNote.Linq; + +namespace Flow.Launcher.Plugin.OneNote.Search +{ + public record struct SearchResult(T item, List? highlightData, int score) where T : IOneNoteItem; + public static class SearchExtensions + { + public static IEnumerable> FuzzySearch(this IEnumerable source, string search, PluginInitContext context) where T: IOneNoteItem + { + foreach (var item in source) + { + MatchResult match = context.API.FuzzySearch(search, item.Name); + if (match.IsSearchPrecisionScoreMet()) + { + yield return new SearchResult(item, match.MatchData, match.Score); + } + } + } + public static IEnumerable FilterBySettings(this IEnumerable source, Settings settings) where T : IOneNoteItem + { + foreach (var item in source) + { + var success = true; + if (settings.ShowEncrypted && item is OneNoteSection section) + { + success = !section.Encrypted; + } + + if (settings.ShowRecycleBin && item.IsInRecycleBin()) + { + success = false; + } + + if (success) + { + yield return item; + } + } + } + + public static bool StartsWithOrd(this string str, string value) + { + return str.StartsWith(value, StringComparison.Ordinal); + } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs new file mode 100644 index 0000000..c7d7052 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Flow.Launcher.Plugin.OneNote.Search +{ + public class SearchManager + { + private readonly TitleSearch titleSearch; + + private readonly NotebookExplorer notebookExplorer; + private readonly Settings settings; + + public SearchManager(Settings settings) + { + this.settings = settings; + titleSearch = new TitleSearch + { + KeywordGetter = () => settings.Keywords.TitleSearch, + }; + notebookExplorer = new NotebookExplorer + { + KeywordGetter = () => settings.Keywords.NotebookExplorer, + }; + + } + public List Query(Query query) + { + //PluginState ps; + var r = query.Search switch + { + { } search when search.StartsWith(titleSearch.Keyword) => titleSearch.GetResults(search), + //string search when search.StartsWith(settings.Keywords.TitleSearch) => TitleSearch(ps, settings.Keywords.TitleSearch, search) + + }; + return null; + } + } + + public record PluginState(PluginInitContext Context, Settings Settings, ResultCreator ResultCreator) + { + public Keywords Keywords => Settings.Keywords; + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs new file mode 100644 index 0000000..c73982d --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Linq; +using Odotocodot.OneNote.Linq; + +namespace Flow.Launcher.Plugin.OneNote.Search +{ + public class TitleSearch : SearchBase + { + public override List GetResults(string query) + { + return Filter(query, null, OneNoteApplication.GetNotebooks()); + } + + public List Filter(string query, IOneNoteItem? parent, IEnumerable collection) + { + if (query.Length == Keyword.Length && parent == null) + return resultCreator.SearchingByTitle(); + + var currentSearch = query[Keyword.Length..]; + + return collection.Traverse() + .FilterBySettings(settings) + .FuzzySearch(currentSearch, context) + .Select(x => resultCreator.CreateOneNoteItemResult(x.item, false, x.highlightData, x.score)) + .ToList(); + } + + public static List Filter(string query, IOneNoteItem? parent, IEnumerable collection, PluginInitContext context, Settings settings, ResultCreator resultCreator) + { + if (query.Length == settings.Keywords.TitleSearch.Length && parent == null) + return resultCreator.SearchingByTitle(); + + var currentSearch = query[settings.Keywords.TitleSearch.Length..]; + + return collection.Traverse() + .FilterBySettings(settings) + .FuzzySearch(currentSearch, context) + .Select(x => resultCreator.CreateOneNoteItemResult(x.item, false, x.highlightData, x.score)) + .ToList(); + } + } +} \ No newline at end of file From aafa81fce2fa8e5a34c766bf45d6709b9c7d49d2 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sun, 20 Jul 2025 22:39:33 +0100 Subject: [PATCH 04/29] Refactor search manager (cont.) --- Flow.Launcher.Plugin.OneNote/Main.cs | 6 +- Flow.Launcher.Plugin.OneNote/ResultCreator.cs | 12 +- .../Search/DefaultSearch.cs | 2 + .../Search/NotebookExplorer.cs | 40 ++-- .../Search/RecentPages.cs | 2 + .../Search/SearchBase.cs | 11 +- .../Search/SearchExtensions.cs | 1 + .../Search/SearchManager.cs | 45 ++--- .../Search/TitleSearch.cs | 39 ++-- .../SearchManager.NotebookExplorer.cs | 188 ------------------ Flow.Launcher.Plugin.OneNote/SearchManager.cs | 105 ---------- 11 files changed, 79 insertions(+), 372 deletions(-) delete mode 100644 Flow.Launcher.Plugin.OneNote/SearchManager.NotebookExplorer.cs delete mode 100644 Flow.Launcher.Plugin.OneNote/SearchManager.cs diff --git a/Flow.Launcher.Plugin.OneNote/Main.cs b/Flow.Launcher.Plugin.OneNote/Main.cs index e17deb9..4f3e7a5 100644 --- a/Flow.Launcher.Plugin.OneNote/Main.cs +++ b/Flow.Launcher.Plugin.OneNote/Main.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using System.Windows.Controls; using Flow.Launcher.Plugin.OneNote.Icons; +using Flow.Launcher.Plugin.OneNote.Search; using Flow.Launcher.Plugin.OneNote.UI.Views; using Odotocodot.OneNote.Linq; namespace Flow.Launcher.Plugin.OneNote @@ -22,7 +23,8 @@ public class Main : IAsyncPlugin, IContextMenu, ISettingProvider, IDisposable private static Main instance; private Query currentQuery; - + private Search.SearchManager sm2; + public Task InitAsync(PluginInitContext context) { this.context = context; @@ -65,7 +67,7 @@ public async Task> QueryAsync(Query query, CancellationToken token) await init; - return searchManager.Query(query); + return searchManager.Query(query.Search); } [Obsolete("Use PluginInitContext.API.ReQuery")] diff --git a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs index 6e90240..5f845a8 100644 --- a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs +++ b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs @@ -411,28 +411,28 @@ public List ContextMenu(Result selectedResult) return results; } - public List NoItemsInCollection(List results, IOneNoteItem? parent) + public List EmptyCollection(List results, IOneNoteItem? parent) { // parent can be null if the collection only contains notebooks. switch (parent) { case INotebookOrSectionGroup: // Can create section/section group - results.Add(NoItemsInCollectionResult("section", iconProvider.NewSection, "(unencrypted) section")); - results.Add(NoItemsInCollectionResult("section group", iconProvider.NewSectionGroup)); + results.Add(EmptyCollectionResult("section", iconProvider.NewSection, "(unencrypted) section")); + results.Add(EmptyCollectionResult("section group", iconProvider.NewSectionGroup)); break; case OneNoteSection section: // Can create page if (!section.Locked) { - results.Add(NoItemsInCollectionResult("page", iconProvider.NewPage, section: section)); + results.Add(EmptyCollectionResult("page", iconProvider.NewPage, section: section)); } break; } return results; - Result NoItemsInCollectionResult(string title, string iconPath, string? subTitle = null, OneNoteSection? section = null) + Result EmptyCollectionResult(string title, string iconPath, string? subTitle = null, OneNoteSection? section = null) { return new Result { @@ -463,7 +463,7 @@ public List InvalidQuery(bool includeSubtitle = true) } public List SearchingByTitle() { - return SingleResult($"Now searching by title.", null, iconProvider.Search); + return SingleResult("Now searching by title.", string.Empty, iconProvider.Search); } private static List SingleResult(string title, string subTitle, string iconPath) diff --git a/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs b/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs index 669e90a..d8969a8 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs @@ -6,6 +6,8 @@ namespace Flow.Launcher.Plugin.OneNote.Search { public class DefaultSearch : SearchBase { + public DefaultSearch(PluginInitContext context, Settings settings, ResultCreator resultCreator) : base(context, settings, resultCreator, null) { } + public override List GetResults(string query) { if (!char.IsLetterOrDigit(query[0])) diff --git a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs index 1ec2de0..025372f 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs @@ -8,25 +8,24 @@ namespace Flow.Launcher.Plugin.OneNote.Search { public class NotebookExplorer : SearchBase { + private readonly TitleSearch titleSearch; + public NotebookExplorer(PluginInitContext context, Settings settings, ResultCreator resultCreator, TitleSearch titleSearch) : base(context, + settings, resultCreator, () => settings.Keywords.NotebookExplorer) + { + this.titleSearch = titleSearch; + } + public override List GetResults(string query) { - if (ValidateSearch(query, out string? search, out IOneNoteItem? parent, out IEnumerable collection)) + if (!ValidateSearch(query, out string? search, out IOneNoteItem? parent, out IEnumerable collection)) return resultCreator.InvalidQuery(false); List results = search switch { - // Empty search so show all in collection - string when string.IsNullOrWhiteSpace(search) => ShowAll(parent, collection), - - // Search by title - not null when search.StartsWith(Keywords.TitleSearch) && parent is not OneNotePage - => TitleSearch.Filter(search, parent, collection, context, settings, resultCreator), - - // Scoped search - not null when search.StartsWith(Keywords.ScopedSearch) && parent is INotebookOrSectionGroup => ScopedSearch(search, parent), - - // Default search - _ => Explorer(search, parent, collection), + { } when search.StartsWith(Keywords.TitleSearch) && parent is not OneNotePage => titleSearch.Filter(search, parent, collection), + { } when search.StartsWith(Keywords.ScopedSearch) && parent is INotebookOrSectionGroup => ScopedSearch(search, parent), + { } when !string.IsNullOrWhiteSpace(search) => Explorer(search, parent, collection), + _ => ShowAll(parent, collection), }; if (parent == null) @@ -36,8 +35,8 @@ not null when search.StartsWith(Keywords.ScopedSearch) && parent is INotebookOrS result.Title = $"Open \"{parent.Name}\" in OneNote"; result.SubTitle = search switch { - not null when search.StartsWith(Keywords.TitleSearch) => $"Now searching by title in \"{parent.Name}\"", - not null when search.StartsWith(Keywords.ScopedSearch) => $"Now searching all pages in \"{parent.Name}\"", + { } when search.StartsWith(Keywords.TitleSearch) => $"Now searching by title in \"{parent.Name}\"", + { } when search.StartsWith(Keywords.ScopedSearch) => $"Now searching all pages in \"{parent.Name}\"", _ => $"Use \'{Keywords.ScopedSearch}\' to search this item. Use \'{Keywords.TitleSearch}\' to search by title in this item", }; @@ -52,7 +51,6 @@ private bool ValidateSearch(string query, out string? lastSearch, out IOneNoteIt collection = OneNoteApplication.GetNotebooks(); string search = query[(query.IndexOf(Keywords.NotebookExplorer, StringComparison.Ordinal) + Keywords.NotebookExplorer.Length)..]; - const string separator = Keywords.NotebookExplorerSeparator; var currIndex = search.IndexOf(separator, StringComparison.Ordinal); var prevIndex = 0; @@ -80,7 +78,7 @@ private List ShowAll(IOneNoteItem? parent, IEnumerable col .Select(item => resultCreator.CreateOneNoteItemResult(item, true)) .ToList(); - return results.Any() ? results : resultCreator.NoItemsInCollection(results, parent); + return results.Any() ? results : resultCreator.EmptyCollection(results, parent); } private List ScopedSearch(string query, IOneNoteItem parent) @@ -93,9 +91,11 @@ private List ScopedSearch(string query, IOneNoteItem parent) string currentSearch = query[Keywords.TitleSearch.Length..]; - return OneNoteApplication.FindPages(currentSearch, parent) - .Select(pg => resultCreator.CreatePageResult(pg, currentSearch)) - .ToList(); + var results = OneNoteApplication.FindPages(currentSearch, parent) + .Select(pg => resultCreator.CreatePageResult(pg, currentSearch)) + .ToList(); + + return results.Any() ? results : ResultCreator.NoMatchesFound(); } private List Explorer(string search, IOneNoteItem? parent, IEnumerable collection) diff --git a/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs b/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs index 1f4e4a1..76cc838 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs @@ -6,6 +6,8 @@ namespace Flow.Launcher.Plugin.OneNote.Search { public class RecentPages : SearchBase { + public RecentPages(PluginInitContext context, Settings settings, ResultCreator resultCreator) : base(context, settings, resultCreator, () => settings.Keywords.RecentPages) { } + public override List GetResults(string query) { int count = settings.DefaultRecentsCount; diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs index df96ac4..b120aec 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs @@ -8,8 +8,15 @@ public abstract class SearchBase protected readonly PluginInitContext context; protected readonly Settings settings; protected readonly ResultCreator resultCreator; - public Func KeywordGetter { get; init; } - public string Keyword => KeywordGetter(); + private readonly Func keywordGetter; + protected SearchBase(PluginInitContext context, Settings settings, ResultCreator resultCreator, Func keywordGetter) + { + this.context = context; + this.settings = settings; + this.resultCreator = resultCreator; + this.keywordGetter = keywordGetter; + } + public string Keyword => keywordGetter(); protected Keywords Keywords => settings.Keywords; public abstract List GetResults(string query); } diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs index 53a7806..3938e42 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs @@ -41,6 +41,7 @@ public static IEnumerable FilterBySettings(this IEnumerable source, Set } } + //TODO: implement public static bool StartsWithOrd(this string str, string value) { return str.StartsWith(value, StringComparison.Ordinal); diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs index c7d7052..b0c1760 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs @@ -1,43 +1,40 @@ using System.Collections.Generic; -using System.Linq; namespace Flow.Launcher.Plugin.OneNote.Search { public class SearchManager { + private readonly Settings settings; + private readonly TitleSearch titleSearch; - private readonly NotebookExplorer notebookExplorer; - private readonly Settings settings; + private readonly DefaultSearch defaultSearch; + private readonly RecentPages recentPages; - public SearchManager(Settings settings) + public SearchManager(PluginInitContext context, Settings settings, ResultCreator resultCreator) { this.settings = settings; - titleSearch = new TitleSearch - { - KeywordGetter = () => settings.Keywords.TitleSearch, - }; - notebookExplorer = new NotebookExplorer - { - KeywordGetter = () => settings.Keywords.NotebookExplorer, - }; - + titleSearch = new TitleSearch(context, settings, resultCreator); + notebookExplorer = new NotebookExplorer(context, settings, resultCreator, titleSearch); + recentPages = new RecentPages(context, settings, resultCreator); + defaultSearch = new DefaultSearch(context, settings, resultCreator); + } - public List Query(Query query) + + public List Query(string search) { - //PluginState ps; - var r = query.Search switch + return search switch { - { } search when search.StartsWith(titleSearch.Keyword) => titleSearch.GetResults(search), - //string search when search.StartsWith(settings.Keywords.TitleSearch) => TitleSearch(ps, settings.Keywords.TitleSearch, search) - + { } when search.StartsWith(titleSearch.Keyword) => titleSearch.GetResults(search), + { } when search.StartsWith(notebookExplorer.Keyword) => notebookExplorer.GetResults(search), + { } when search.StartsWith(recentPages.Keyword) => recentPages.GetResults(search), + _ => defaultSearch.GetResults(search!), }; - return null; } } - public record PluginState(PluginInitContext Context, Settings Settings, ResultCreator ResultCreator) - { - public Keywords Keywords => Settings.Keywords; - } + // public record PluginState(PluginInitContext Context, Settings Settings, ResultCreator ResultCreator) + // { + // public Keywords Keywords => Settings.Keywords; + // } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs index c73982d..6d2f1f7 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Odotocodot.OneNote.Linq; @@ -6,37 +7,25 @@ namespace Flow.Launcher.Plugin.OneNote.Search { public class TitleSearch : SearchBase { - public override List GetResults(string query) - { - return Filter(query, null, OneNoteApplication.GetNotebooks()); - } - + public TitleSearch(PluginInitContext context, Settings settings, ResultCreator resultCreator) : base(context, settings, resultCreator, + () => settings.Keywords.TitleSearch) { } + + public override List GetResults(string query) => Filter(query, null, OneNoteApplication.GetNotebooks()); + public List Filter(string query, IOneNoteItem? parent, IEnumerable collection) { - if (query.Length == Keyword.Length && parent == null) + if (query.Length == Keyword.Length || parent == null) return resultCreator.SearchingByTitle(); - + var currentSearch = query[Keyword.Length..]; - return collection.Traverse() - .FilterBySettings(settings) - .FuzzySearch(currentSearch, context) - .Select(x => resultCreator.CreateOneNoteItemResult(x.item, false, x.highlightData, x.score)) - .ToList(); - } - - public static List Filter(string query, IOneNoteItem? parent, IEnumerable collection, PluginInitContext context, Settings settings, ResultCreator resultCreator) - { - if (query.Length == settings.Keywords.TitleSearch.Length && parent == null) - return resultCreator.SearchingByTitle(); - - var currentSearch = query[settings.Keywords.TitleSearch.Length..]; + var results = collection.Traverse() + .FilterBySettings(settings) + .FuzzySearch(currentSearch, context) + .Select(x => resultCreator.CreateOneNoteItemResult(x.item, false, x.highlightData, x.score)) + .ToList(); - return collection.Traverse() - .FilterBySettings(settings) - .FuzzySearch(currentSearch, context) - .Select(x => resultCreator.CreateOneNoteItemResult(x.item, false, x.highlightData, x.score)) - .ToList(); + return results.Any() ? results : ResultCreator.NoMatchesFound(); } } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/SearchManager.NotebookExplorer.cs b/Flow.Launcher.Plugin.OneNote/SearchManager.NotebookExplorer.cs deleted file mode 100644 index 764cc51..0000000 --- a/Flow.Launcher.Plugin.OneNote/SearchManager.NotebookExplorer.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Odotocodot.OneNote.Linq; - -namespace Flow.Launcher.Plugin.OneNote -{ - public partial class SearchManager - { - private sealed class NotebookExplorer - { - private readonly SearchManager searchManager; - private readonly ResultCreator resultCreator; - - private Keywords Keywords => searchManager.settings.Keywords; - internal NotebookExplorer(SearchManager searchManager, ResultCreator resultCreator) - { - this.searchManager = searchManager; - this.resultCreator = resultCreator; - } - - internal List Query(Query query) - { - var results = new List(); - - string fullSearch = query.Search[(query.Search.IndexOf(Keywords.NotebookExplorer, StringComparison.Ordinal) + Keywords.NotebookExplorer.Length)..]; - - IOneNoteItem? parent = null; - IEnumerable collection = OneNoteApplication.GetNotebooks(); - - // // Validate Search Version 1 - string[] searches = fullSearch.Split(Keywords.NotebookExplorerSeparator, StringSplitOptions.None); - // for (int i = -1; i < searches.Length - 1; i++) - // { - // if (i < 0) - // { - // continue; - // } - // - // parent = collection.FirstOrDefault(item => item.Name.Equals(searches[i])); - // if (parent == null) - // { - // return results; - // } - // - // collection = parent.Children; - // } - - // Validate Search Version 2 - var separator = Keywords.NotebookExplorerSeparator; - var currIndex = fullSearch.IndexOf(separator, StringComparison.Ordinal); - var prevIndex = 0; - while (currIndex != -1) - { - //var itemName = fullSearch[prevIndex..currIndex]; - - parent = collection.FirstOrDefault(item => item.Name == fullSearch[prevIndex..currIndex]); - if (parent == null) - return resultCreator.InvalidQuery(false); - - collection = parent.Children; - - prevIndex = currIndex + 1; - currIndex = fullSearch.IndexOf(separator, currIndex + separator.Length, StringComparison.Ordinal); - } - - - string lastSearch = searches[^1]; - - results = lastSearch switch - { - // Empty search so show all in collection - string search when string.IsNullOrWhiteSpace(search) - => EmptySearch(parent, collection), - - // Search by title - string search when search.StartsWith(Keywords.TitleSearch) && parent is not OneNotePage - => searchManager.TitleSearch(search, parent, collection), - - // Scoped search - string search when search.StartsWith(Keywords.ScopedSearch) && parent is OneNoteNotebook or OneNoteSectionGroup - => ScopedSearch(search, parent), - - // Default search - _ => Explorer(lastSearch, parent, collection), - }; - - if (parent != null) - { - var result = resultCreator.CreateOneNoteItemResult(parent, false, score: 4000); - result.Title = $"Open \"{parent.Name}\" in OneNote"; - result.SubTitle = lastSearch switch - { - string search when search.StartsWith(Keywords.TitleSearch) - => $"Now searching by title in \"{parent.Name}\"", - - string search when search.StartsWith(Keywords.ScopedSearch) - => $"Now searching all pages in \"{parent.Name}\"", - - _ => $"Use \'{Keywords.ScopedSearch}\' to search this item. Use \'{Keywords.TitleSearch}\' to search by title in this item", - }; - - results.Add(result); - } - - return results; - } - - private List EmptySearch(IOneNoteItem parent, IEnumerable collection) - { - List results = collection.Where(searchManager.SettingsCheck) - .Select(item => resultCreator.CreateOneNoteItemResult(item, true)) - .ToList(); - if (results.Any()) - return results; - return resultCreator.NoItemsInCollection(results, parent); - } - - private List ScopedSearch(string query, IOneNoteItem parent) - { - if (query.Length == Keywords.ScopedSearch.Length) - { - return ResultCreator.NoMatchesFound(); - } - - if (!char.IsLetterOrDigit(query[Keywords.ScopedSearch.Length])) - { - return resultCreator.InvalidQuery(); - } - - string currentSearch = query[Keywords.TitleSearch.Length..]; - - var results = OneNoteApplication.FindPages(currentSearch, parent) - .Select(pg => resultCreator.CreatePageResult(pg, currentSearch)) - .ToList(); - - if (!results.Any()) - { - results = ResultCreator.NoMatchesFound(); - } - - return results; - } -#nullable enable - private List Explorer(string search, IOneNoteItem? parent, IEnumerable collection) - { - List? highlightData = null; - int score = 0; - - var results = collection.Where(searchManager.SettingsCheck) - .Where(item => searchManager.FuzzySearch(item.Name, search, out highlightData, out score)) - .Select(item => resultCreator.CreateOneNoteItemResult(item, true, highlightData, score)) - .ToList(); - - AddCreateNewOneNoteItemResults(search, parent, results); - return results; - } - - private void AddCreateNewOneNoteItemResults(string newItemName, IOneNoteItem? parent, List results) - { - if (results.Any(result => string.Equals(newItemName.Trim(), result.Title, StringComparison.OrdinalIgnoreCase))) - return; - - if (parent?.IsInRecycleBin() == true) - return; - - switch (parent) - { - case null: - results.Add(resultCreator.CreateNewNotebookResult(newItemName)); - break; - case OneNoteNotebook: - case OneNoteSectionGroup: - results.Add(resultCreator.CreateNewSectionResult(newItemName, parent)); - results.Add(resultCreator.CreateNewSectionGroupResult(newItemName, parent)); - break; - case OneNoteSection section: - if (!section.Locked) - { - results.Add(resultCreator.CreateNewPageResult(newItemName, section)); - } - - break; - } - } - } - } -} diff --git a/Flow.Launcher.Plugin.OneNote/SearchManager.cs b/Flow.Launcher.Plugin.OneNote/SearchManager.cs deleted file mode 100644 index fbd7faa..0000000 --- a/Flow.Launcher.Plugin.OneNote/SearchManager.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Odotocodot.OneNote.Linq; - -namespace Flow.Launcher.Plugin.OneNote -{ - public partial class SearchManager - { - private readonly PluginInitContext context; - private readonly Settings settings; - private readonly ResultCreator resultCreator; - private readonly NotebookExplorer notebookExplorer; - - public SearchManager(PluginInitContext context, Settings settings, ResultCreator resultCreator) - { - this.context = context; - this.settings = settings; - this.resultCreator = resultCreator; - notebookExplorer = new NotebookExplorer(this, resultCreator); - } - - internal List Query(Query query) - { - return query.Search switch - { - string s when s.StartsWith(settings.Keywords.RecentPages) - => RecentPages(s), - - string s when s.StartsWith(settings.Keywords.NotebookExplorer) - => notebookExplorer.Query(query), - - string s when s.StartsWith(settings.Keywords.TitleSearch) - => TitleSearch(s, null, OneNoteApplication.GetNotebooks()), - - _ => DefaultSearch(query.Search), - }; - } - - private List DefaultSearch(string query) - { - // Check for invalid start of query i.e. symbols - if (!char.IsLetterOrDigit(query[0])) - { - return resultCreator.InvalidQuery(); - } - - var results = OneNoteApplication.FindPages(query) - .Select(pg => resultCreator.CreatePageResult(pg, query)); - - return results.Any() ? results.ToList() : ResultCreator.NoMatchesFound(); - } - - private List TitleSearch(string query, IOneNoteItem parent, IEnumerable currentCollection) - { - if (query.Length == settings.Keywords.TitleSearch.Length && parent == null) - { - return resultCreator.SearchingByTitle(); - } - - List highlightData = null; - int score = 0; - - var currentSearch = query[settings.Keywords.TitleSearch.Length..]; - - var results = currentCollection.Traverse(item => SettingsCheck(item) && FuzzySearch(item.Name, currentSearch, out highlightData, out score)) - .Select(item => resultCreator.CreateOneNoteItemResult(item, false, highlightData, score)) - .ToList(); - - return results.Any() ? results : ResultCreator.NoMatchesFound(); - } - - private List RecentPages(string query) - { - int count = settings.DefaultRecentsCount; - - if (query.Length > settings.Keywords.RecentPages.Length && int.TryParse(query[settings.Keywords.RecentPages.Length..], out int userChosenCount)) - count = userChosenCount; - - return OneNoteApplication.GetNotebooks() - .GetPages() - .Where(SettingsCheck) - .OrderByDescending(pg => pg.LastModified) - .Take(count) - .Select(resultCreator.CreateRecentPageResult) - .ToList(); - } - private bool FuzzySearch(string itemName, string search, out List highlightData, out int score) - { - var matchResult = context.API.FuzzySearch(search, itemName); - highlightData = matchResult.MatchData; - score = matchResult.Score; - return matchResult.IsSearchPrecisionScoreMet(); - } - private bool SettingsCheck(IOneNoteItem item) - { - bool success = true; - if (!settings.ShowEncrypted && item is OneNoteSection section) - success = !section.Encrypted; - - if (!settings.ShowRecycleBin && item.IsInRecycleBin()) - success = false; - return success; - } - } -} From 9ee9ae8d32b07c0148cf465c916724813d522e63 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sat, 26 Jul 2025 16:35:50 +0100 Subject: [PATCH 05/29] Refactor keywords --- Flow.Launcher.Plugin.OneNote/Keywords.cs | 47 +++++++++++++++++-- .../UI/RelayCommand.cs | 1 - .../UI/ViewModels/ChangeKeywordViewModel.cs | 15 +++--- .../UI/ViewModels/KeywordViewModel.cs | 40 +++++----------- .../UI/ViewModels/SettingsViewModel.cs | 7 ++- 5 files changed, 68 insertions(+), 42 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/Keywords.cs b/Flow.Launcher.Plugin.OneNote/Keywords.cs index 0273790..deecd82 100644 --- a/Flow.Launcher.Plugin.OneNote/Keywords.cs +++ b/Flow.Launcher.Plugin.OneNote/Keywords.cs @@ -1,11 +1,48 @@ -namespace Flow.Launcher.Plugin.OneNote +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Flow.Launcher.Plugin.OneNote { + public class Keywords { public const string NotebookExplorerSeparator = "\\"; - public string NotebookExplorer { get; set; } = $"nb:{NotebookExplorerSeparator}"; - public string RecentPages { get; set; } = "rp:"; - public string TitleSearch { get; set; } = "*"; - public string ScopedSearch { get; set; } = ">"; + public Keyword NotebookExplorer { get; set; } = new($"nb:{NotebookExplorerSeparator}"); + public Keyword RecentPages { get; set; } = new ("rp:"); + public Keyword TitleSearch { get; set; } = new ("*"); + public Keyword ScopedSearch { get; set; } = new (">"); + } + + [JsonConverter(typeof(KeywordJsonConverter))] + public class Keyword : BaseModel + { + public Keyword(string value) + { + Value = value; + } + public string Value { get; private set; } + + public void ChangeKeyword(string newValue) + { + Value = newValue; + OnPropertyChanged(nameof(Value)); + } + + public int Length => Value.Length; + public static implicit operator string(Keyword keyword) => keyword.Value; + public override string ToString() => Value; + } + + //Needed for legacy as keywords where just saved as a string + public class KeywordJsonConverter : JsonConverter + { + public override Keyword Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + => new(JsonSerializer.Deserialize(ref reader, options)); + + public override void Write(Utf8JsonWriter writer, Keyword value, JsonSerializerOptions options) + => JsonSerializer.Serialize(writer, value.Value, options); + } + } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs b/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs index e462b62..79c89fa 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs @@ -1,4 +1,3 @@ -#nullable enable using System; using System.Windows.Input; diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs index 9518866..ef8dad8 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs @@ -10,18 +10,19 @@ public class ChangeKeywordViewModel : Model private readonly KeywordViewModel[] keywords; private readonly Action closeAction; - private string errorMessage; + private string? errorMessage; + public ChangeKeywordViewModel(SettingsViewModel settingsViewModel, PluginInitContext context, Action close) { this.context = context; closeAction = close; keywords = settingsViewModel.Keywords; - SelectedKeyword = settingsViewModel.SelectedKeyword; + SelectedKeyword = settingsViewModel.SelectedKeyword!; ChangeKeywordCommand = new RelayCommand( keyword => ChangeKeyword((string)keyword), keyword => CanChangeKeyword((string)keyword)); - CloseCommand = new RelayCommand( _=> closeAction?.Invoke()); + CloseCommand = new RelayCommand(_ => closeAction.Invoke()); } public KeywordViewModel SelectedKeyword { get; } @@ -29,7 +30,7 @@ public ChangeKeywordViewModel(SettingsViewModel settingsViewModel, PluginInitCon public ICommand ChangeKeywordCommand { get; } - public string ErrorMessage + public string? ErrorMessage { get => errorMessage; private set => SetProperty(ref errorMessage, value); @@ -50,7 +51,7 @@ private bool CanChangeKeyword(string newKeyword) return false; } - var alreadySetKeyword = keywords.FirstOrDefault(k => k.Keyword == newKeyword); + KeywordViewModel? alreadySetKeyword = keywords.FirstOrDefault(k => k.Keyword == newKeyword); if (alreadySetKeyword != null) { ErrorMessage = $"The new keyword is already set for {alreadySetKeyword.Name}."; @@ -63,9 +64,9 @@ private bool CanChangeKeyword(string newKeyword) private void ChangeKeyword(string newKeyword) { - SelectedKeyword.Keyword = newKeyword.Trim(); + SelectedKeyword.Keyword.ChangeKeyword(newKeyword.Trim()); context.API.SaveSettingJsonStorage(); - closeAction?.Invoke(); + closeAction.Invoke(); } } diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs index 9cf4f98..d4e0f89 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs @@ -1,36 +1,20 @@ -using System.Reflection; -using System.Linq; -using Humanizer; - -namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels +namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels { public class KeywordViewModel : BaseModel { - private object Instance { get; init; } - private PropertyInfo PropertyInfo { get; init; } - public string Name { get; private init; } - - public string Keyword + public KeywordViewModel(string keywordName, Keyword keyword) { - get => (string)PropertyInfo.GetValue(Instance); - set + Name = keywordName; + Keyword = keyword; + keyword.PropertyChanged += (_, args) => { - PropertyInfo.SetValue(Instance, value, null); - OnPropertyChanged(); - } - } - - public static KeywordViewModel[] GetKeywordViewModels(Keywords keywords) - { - return keywords.GetType() - .GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(p => new KeywordViewModel - { - Instance = keywords, - PropertyInfo = p, - Name = p.Name.Humanize(LetterCasing.Title) - }) - .ToArray(); + if (args.PropertyName == nameof(Keyword.Value)) + { + OnPropertyChanged(nameof(Keyword)); + } + }; } + public string Name { get; private init; } + public Keyword Keyword { get; } } } diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs index 5200407..15328e9 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs @@ -1,4 +1,5 @@ using System.Linq; +using System.Reflection; using System.Threading.Tasks; using System.Windows.Input; using Flow.Launcher.Plugin.OneNote.Icons; @@ -16,7 +17,11 @@ public SettingsViewModel(PluginInitContext context, Settings settings, IconProvi { this.iconProvider = iconProvider; Settings = settings; - Keywords = KeywordViewModel.GetKeywordViewModels(settings.Keywords); + Keywords = settings.Keywords + .GetType() + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Select(p => new KeywordViewModel(p.Name.Humanize(LetterCasing.Title), (Keyword)p.GetValue(settings.Keywords)!)) + .ToArray(); IconThemes = IconThemeViewModel.GetIconThemeViewModels(context); EditCommand = new RelayCommand( From 88b678e4b3f0350f4858adf43d072a7cee87b57f Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sat, 26 Jul 2025 18:41:58 +0100 Subject: [PATCH 06/29] Refactor view models --- .../UI/RelayCommand.cs | 34 ++++++++++++++++--- .../UI/ViewModels/ChangeKeywordViewModel.cs | 9 +++-- .../UI/ViewModels/IconThemeViewModel.cs | 17 ++++------ .../UI/ViewModels/KeywordViewModel.cs | 2 +- .../UI/ViewModels/NewOneNotePageViewModel.cs | 11 +++--- .../UI/ViewModels/SettingsViewModel.cs | 23 +++++++------ 6 files changed, 59 insertions(+), 37 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs b/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs index 79c89fa..180b294 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs @@ -5,8 +5,8 @@ namespace Flow.Launcher.Plugin.OneNote.UI { public sealed class RelayCommand : ICommand { - private readonly Action execute; - private readonly Predicate? canExecute; + private readonly Action execute; + private readonly Func? canExecute; public event EventHandler? CanExecuteChanged { @@ -14,7 +14,7 @@ public event EventHandler? CanExecuteChanged remove => CommandManager.RequerySuggested -= value; } - public RelayCommand(Action execute, Predicate? canExecute = null) + public RelayCommand(Action execute, Func? canExecute = null) { this.execute = execute; this.canExecute = canExecute; @@ -22,12 +22,36 @@ public RelayCommand(Action execute, Predicate? canExecute = nu public bool CanExecute(object? parameter) { - return canExecute?.Invoke(parameter) != false; + return canExecute?.Invoke() != false; } public void Execute(object? parameter) { - execute(parameter); + execute(); } } + + public sealed class RelayCommand : ICommand + { + private readonly Action execute; + private readonly Predicate? canExecute; + + public event EventHandler? CanExecuteChanged + { + add => CommandManager.RequerySuggested += value; + remove => CommandManager.RequerySuggested -= value; + } + + public RelayCommand(Action execute, Predicate? canExecute = null) + { + this.execute = execute; + this.canExecute = canExecute; + } + + public bool CanExecute(T? parameter) => canExecute?.Invoke(parameter) != false; + public void Execute(T? parameter) => execute(parameter); + + public bool CanExecute(object? parameter) => CanExecute((T?)parameter); + public void Execute(object? parameter) => Execute((T?)parameter); + } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs index ef8dad8..5901b0d 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs @@ -19,10 +19,8 @@ public ChangeKeywordViewModel(SettingsViewModel settingsViewModel, PluginInitCon closeAction = close; keywords = settingsViewModel.Keywords; SelectedKeyword = settingsViewModel.SelectedKeyword!; - ChangeKeywordCommand = new RelayCommand( - keyword => ChangeKeyword((string)keyword), - keyword => CanChangeKeyword((string)keyword)); - CloseCommand = new RelayCommand(_ => closeAction.Invoke()); + ChangeKeywordCommand = new RelayCommand(ChangeKeyword!, CanChangeKeyword); + CloseCommand = new RelayCommand(closeAction.Invoke); } public KeywordViewModel SelectedKeyword { get; } @@ -36,7 +34,7 @@ public string? ErrorMessage private set => SetProperty(ref errorMessage, value); } - private bool CanChangeKeyword(string newKeyword) + private bool CanChangeKeyword(string? newKeyword) { if (string.IsNullOrWhiteSpace(newKeyword)) { @@ -66,6 +64,7 @@ private void ChangeKeyword(string newKeyword) { SelectedKeyword.Keyword.ChangeKeyword(newKeyword.Trim()); context.API.SaveSettingJsonStorage(); + context.API.ReQuery(); closeAction.Invoke(); } } diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/IconThemeViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/IconThemeViewModel.cs index f53bd89..d3a8fae 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/IconThemeViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/IconThemeViewModel.cs @@ -1,24 +1,23 @@ using System; -using System.Linq; using Flow.Launcher.Plugin.OneNote.Icons; namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels { public class IconThemeViewModel { - private IconThemeViewModel(IconTheme iconTheme, PluginInitContext context) + public IconThemeViewModel(IconTheme iconTheme, PluginInitContext context) { IconTheme = iconTheme; if (iconTheme == IconTheme.System) { Name = "FL Default"; - ImageUri = GetUri(IconTheme.Light.ToString(), context); - ImageUri2 = GetUri(IconTheme.Dark.ToString(), context); + ImageUri = GetUri(nameof(IconTheme.Light), context); + ImageUri2 = GetUri(nameof(IconTheme.Dark), context); Tooltip = "Matches Flow Launcher's app theme"; } else { - Name = Enum.GetName(iconTheme); + Name = iconTheme.ToString(); ImageUri = GetUri(Name, context); } } @@ -29,11 +28,7 @@ private static Uri GetUri(string theme, PluginInitContext context) => public string Name { get; } public IconTheme IconTheme { get; } public Uri ImageUri { get; } - public Uri ImageUri2 { get; } - public string Tooltip { get; } - - public static IconThemeViewModel[] GetIconThemeViewModels(PluginInitContext context) => - Enum.GetValues().Select(iconTheme => new IconThemeViewModel(iconTheme, context)).ToArray(); + public Uri? ImageUri2 { get; } + public string? Tooltip { get; } } - } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs index d4e0f89..28b5d6d 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs @@ -14,7 +14,7 @@ public KeywordViewModel(string keywordName, Keyword keyword) } }; } - public string Name { get; private init; } + public string Name { get; } public Keyword Keyword { get; } } } diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs index 651bcb0..8ca7a01 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs @@ -7,8 +7,8 @@ namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels { public class NewOneNotePageViewModel : Model { - private string pageTitle; - private string pageContent; + private string pageTitle = string.Empty; + private string pageContent = string.Empty; private readonly OneNoteSection section; private readonly PluginInitContext context; @@ -17,8 +17,8 @@ public NewOneNotePageViewModel(PluginInitContext context, OneNoteSection section this.context = context; this.section = section; PageTitle = pageTitle; - CreateCommand = new RelayCommand(_ => CreatePage(false)); - CreateAndOpenCommand = new RelayCommand(_ => CreatePage(true)); + CreateCommand = new RelayCommand(() => CreatePage(false)); + CreateAndOpenCommand = new RelayCommand(() => CreatePage(true)); } private void CreatePage(bool openImmediately) @@ -29,7 +29,7 @@ private void CreatePage(bool openImmediately) var xmlWrap = $""; pageContentXml = pageContentXml.Insert(pageContentXml.IndexOf("", StringComparison.Ordinal), xmlWrap); OneNoteApplication.UpdatePageContent(pageContentXml); - Main.ForceReQuery(); + context.API.ReQuery(); if (openImmediately) { page.OpenInOneNote(); @@ -43,6 +43,7 @@ private void CreatePage(bool openImmediately) $"{context.CurrentPluginMetadata.PluginDirectory}/{IconProvider.Logo}"); } } + public string PageTitle { get => pageTitle; diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs index 15328e9..f1e0898 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Windows.Input; @@ -11,7 +12,7 @@ namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels public class SettingsViewModel : Model { private readonly IconProvider iconProvider; - private KeywordViewModel selectedKeyword; + private KeywordViewModel? selectedKeyword; public SettingsViewModel(PluginInitContext context, Settings settings, IconProvider iconProvider) { @@ -22,18 +23,20 @@ public SettingsViewModel(PluginInitContext context, Settings settings, IconProvi .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(p => new KeywordViewModel(p.Name.Humanize(LetterCasing.Title), (Keyword)p.GetValue(settings.Keywords)!)) .ToArray(); - IconThemes = IconThemeViewModel.GetIconThemeViewModels(context); + IconThemes = Enum.GetValues() + .Select(iconTheme => new IconThemeViewModel(iconTheme, context)) + .ToArray(); EditCommand = new RelayCommand( - _ => new Views.ChangeKeywordWindow(this, context).ShowDialog(), //Avert your eyes! This is not MVVM! - _ => SelectedKeyword != null); + () => new Views.ChangeKeywordWindow(this, context).ShowDialog(), //Avert your eyes! This is not MVVM! + () => SelectedKeyword != null); OpenGeneratedIconsFolderCommand = new RelayCommand( - _ => context.API.OpenDirectory(iconProvider.GeneratedImagesDirectoryInfo.FullName)); + () => context.API.OpenDirectory(iconProvider.GeneratedImagesDirectoryInfo.FullName)); ClearCachedIconsCommand = new RelayCommand( - async _ => await ClearCachedIcons(), - _ => iconProvider.CachedIconCount > 0); + async () => await ClearCachedIcons(), + () => iconProvider.CachedIconCount > 0); iconProvider.PropertyChanged += (_, args) => { @@ -47,7 +50,7 @@ public SettingsViewModel(PluginInitContext context, Settings settings, IconProvi { if (args.PropertyName == nameof(Settings.IconTheme)) { - Main.ForceReQuery(); + context.API.ReQuery(); } }; SelectedKeyword = Keywords[0]; @@ -64,7 +67,7 @@ public SettingsViewModel(PluginInitContext context, Settings settings, IconProvi .Bytes() .Humanize(); - public KeywordViewModel SelectedKeyword + public KeywordViewModel? SelectedKeyword { get => selectedKeyword; set => SetProperty(ref selectedKeyword, value); From 41ad0a4dc0d179f001c2ec4e03fea3a28e9a54fa Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sat, 26 Jul 2025 19:33:14 +0100 Subject: [PATCH 07/29] Update search classes with keyword --- Flow.Launcher.Plugin.OneNote/Main.cs | 1 - .../Search/DefaultSearch.cs | 3 ++- .../Search/NotebookExplorer.cs | 12 ++++++------ Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs | 3 ++- Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs | 8 +++----- .../Search/SearchExtensions.cs | 1 - .../Search/SearchManager.cs | 14 +++----------- Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs | 4 ++-- 8 files changed, 18 insertions(+), 28 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/Main.cs b/Flow.Launcher.Plugin.OneNote/Main.cs index 4f3e7a5..5030ca6 100644 --- a/Flow.Launcher.Plugin.OneNote/Main.cs +++ b/Flow.Launcher.Plugin.OneNote/Main.cs @@ -23,7 +23,6 @@ public class Main : IAsyncPlugin, IContextMenu, ISettingProvider, IDisposable private static Main instance; private Query currentQuery; - private Search.SearchManager sm2; public Task InitAsync(PluginInitContext context) { diff --git a/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs b/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs index d8969a8..ce0e2d5 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs @@ -6,7 +6,8 @@ namespace Flow.Launcher.Plugin.OneNote.Search { public class DefaultSearch : SearchBase { - public DefaultSearch(PluginInitContext context, Settings settings, ResultCreator resultCreator) : base(context, settings, resultCreator, null) { } + public DefaultSearch(PluginInitContext context, Settings settings, ResultCreator resultCreator) + : base(context, settings, resultCreator, null) { } public override List GetResults(string query) { diff --git a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs index 025372f..4d123ca 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs @@ -9,8 +9,8 @@ namespace Flow.Launcher.Plugin.OneNote.Search public class NotebookExplorer : SearchBase { private readonly TitleSearch titleSearch; - public NotebookExplorer(PluginInitContext context, Settings settings, ResultCreator resultCreator, TitleSearch titleSearch) : base(context, - settings, resultCreator, () => settings.Keywords.NotebookExplorer) + public NotebookExplorer(PluginInitContext context, Settings settings, ResultCreator resultCreator, TitleSearch titleSearch) + : base(context, settings, resultCreator, settings.Keywords.NotebookExplorer) { this.titleSearch = titleSearch; } @@ -22,8 +22,8 @@ public override List GetResults(string query) List results = search switch { - { } when search.StartsWith(Keywords.TitleSearch) && parent is not OneNotePage => titleSearch.Filter(search, parent, collection), - { } when search.StartsWith(Keywords.ScopedSearch) && parent is INotebookOrSectionGroup => ScopedSearch(search, parent), + { } when search.StartsWithOrd(Keywords.TitleSearch) && parent is not OneNotePage => titleSearch.Filter(search, parent, collection), + { } when search.StartsWithOrd(Keywords.ScopedSearch) && parent is INotebookOrSectionGroup => ScopedSearch(search, parent), { } when !string.IsNullOrWhiteSpace(search) => Explorer(search, parent, collection), _ => ShowAll(parent, collection), }; @@ -35,8 +35,8 @@ public override List GetResults(string query) result.Title = $"Open \"{parent.Name}\" in OneNote"; result.SubTitle = search switch { - { } when search.StartsWith(Keywords.TitleSearch) => $"Now searching by title in \"{parent.Name}\"", - { } when search.StartsWith(Keywords.ScopedSearch) => $"Now searching all pages in \"{parent.Name}\"", + { } when search.StartsWithOrd(Keywords.TitleSearch) => $"Now searching by title in \"{parent.Name}\"", + { } when search.StartsWithOrd(Keywords.ScopedSearch) => $"Now searching all pages in \"{parent.Name}\"", _ => $"Use \'{Keywords.ScopedSearch}\' to search this item. Use \'{Keywords.TitleSearch}\' to search by title in this item", }; diff --git a/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs b/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs index 76cc838..24442c5 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs @@ -6,7 +6,8 @@ namespace Flow.Launcher.Plugin.OneNote.Search { public class RecentPages : SearchBase { - public RecentPages(PluginInitContext context, Settings settings, ResultCreator resultCreator) : base(context, settings, resultCreator, () => settings.Keywords.RecentPages) { } + public RecentPages(PluginInitContext context, Settings settings, ResultCreator resultCreator) + : base(context, settings, resultCreator, settings.Keywords.RecentPages) { } public override List GetResults(string query) { diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs index b120aec..ac7ae69 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; namespace Flow.Launcher.Plugin.OneNote.Search @@ -8,15 +7,14 @@ public abstract class SearchBase protected readonly PluginInitContext context; protected readonly Settings settings; protected readonly ResultCreator resultCreator; - private readonly Func keywordGetter; - protected SearchBase(PluginInitContext context, Settings settings, ResultCreator resultCreator, Func keywordGetter) + public readonly Keyword Keyword; + protected SearchBase(PluginInitContext context, Settings settings, ResultCreator resultCreator, Keyword keyword) { this.context = context; this.settings = settings; this.resultCreator = resultCreator; - this.keywordGetter = keywordGetter; + Keyword = keyword; } - public string Keyword => keywordGetter(); protected Keywords Keywords => settings.Keywords; public abstract List GetResults(string query); } diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs index 3938e42..53a7806 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs @@ -41,7 +41,6 @@ public static IEnumerable FilterBySettings(this IEnumerable source, Set } } - //TODO: implement public static bool StartsWithOrd(this string str, string value) { return str.StartsWith(value, StringComparison.Ordinal); diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs index b0c1760..07fc3e7 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs @@ -4,8 +4,6 @@ namespace Flow.Launcher.Plugin.OneNote.Search { public class SearchManager { - private readonly Settings settings; - private readonly TitleSearch titleSearch; private readonly NotebookExplorer notebookExplorer; private readonly DefaultSearch defaultSearch; @@ -13,7 +11,6 @@ public class SearchManager public SearchManager(PluginInitContext context, Settings settings, ResultCreator resultCreator) { - this.settings = settings; titleSearch = new TitleSearch(context, settings, resultCreator); notebookExplorer = new NotebookExplorer(context, settings, resultCreator, titleSearch); recentPages = new RecentPages(context, settings, resultCreator); @@ -25,16 +22,11 @@ public List Query(string search) { return search switch { - { } when search.StartsWith(titleSearch.Keyword) => titleSearch.GetResults(search), - { } when search.StartsWith(notebookExplorer.Keyword) => notebookExplorer.GetResults(search), - { } when search.StartsWith(recentPages.Keyword) => recentPages.GetResults(search), + { } when search.StartsWithOrd(titleSearch.Keyword) => titleSearch.GetResults(search), + { } when search.StartsWithOrd(notebookExplorer.Keyword) => notebookExplorer.GetResults(search), + { } when search.StartsWithOrd(recentPages.Keyword) => recentPages.GetResults(search), _ => defaultSearch.GetResults(search!), }; } } - - // public record PluginState(PluginInitContext Context, Settings Settings, ResultCreator ResultCreator) - // { - // public Keywords Keywords => Settings.Keywords; - // } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs index 6d2f1f7..b4e08aa 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs @@ -7,8 +7,8 @@ namespace Flow.Launcher.Plugin.OneNote.Search { public class TitleSearch : SearchBase { - public TitleSearch(PluginInitContext context, Settings settings, ResultCreator resultCreator) : base(context, settings, resultCreator, - () => settings.Keywords.TitleSearch) { } + public TitleSearch(PluginInitContext context, Settings settings, ResultCreator resultCreator) + : base(context, settings, resultCreator, settings.Keywords.TitleSearch) { } public override List GetResults(string query) => Filter(query, null, OneNoteApplication.GetNotebooks()); From 35995a15b7c8d133e026f6ff13277df19837985a Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sun, 27 Jul 2025 17:47:08 +0100 Subject: [PATCH 08/29] Refactor ResultCreator --- Flow.Launcher.Plugin.OneNote/ResultCreator.cs | 165 +++++++----------- .../Search/NotebookExplorer.cs | 6 +- 2 files changed, 65 insertions(+), 106 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs index 5f845a8..c8c3579 100644 --- a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs +++ b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs @@ -49,9 +49,12 @@ private string GetTitle(IOneNoteItem item, List? highlightData) return title; } - private string GetAutoCompleteText(IOneNoteItem item) - => $"{ActionKeyword} {settings.Keywords.NotebookExplorer}{GetNicePath(item, Keywords.NotebookExplorerSeparator)}{Keywords.NotebookExplorerSeparator}"; - + private string GetAutoCompleteText(IOneNoteItem item) //Auto complete text if in notebook explorer + { + string slash = item is OneNotePage ? string.Empty : Keywords.NotebookExplorerSeparator; + return $"{ActionKeyword} {settings.Keywords.NotebookExplorer}{GetNicePath(item, Keywords.NotebookExplorerSeparator)}{slash}"; + } + public List EmptyQuery() { return new List @@ -224,12 +227,33 @@ public Result CreatePageResult(OneNotePage page, string query) public Result CreateRecentPageResult(OneNotePage page) { - var result = CreateOneNoteItemResult(page, false, null); + var result = CreateOneNoteItemResult(page, false); result.SubTitle = $"{page.LastModified.Humanize()} | {result.SubTitle}"; result.IcoPath = iconProvider.Recent; + result.AddSelectedCount = false; return result; } + + private bool CreateNewItem(T parent, string name, bool validTitle, ActionContext c, Func createAction) where T : IOneNoteItem + { + if (!validTitle) + { + return false; + } + + bool showOneNote = !c.SpecialKeyState.CtrlPressed; + + createAction(parent, name, showOneNote); + + context.API.ReQuery(); + + if(showOneNote) + WindowHelper.FocusOneNote(); + + return showOneNote; + } + public Result CreateNewPageResult(string newPageName, OneNoteSection section) { newPageName = newPageName.Trim(); @@ -240,22 +264,11 @@ public Result CreateNewPageResult(string newPageName, OneNoteSection section) AutoCompleteText = $"{GetAutoCompleteText(section)}{newPageName}", IcoPath = iconProvider.NewPage, PreviewPanel = GetNewPagePreviewPanel(section, newPageName), - Action = c => - { - bool showOneNote = !c.SpecialKeyState.CtrlPressed; - - OneNoteApplication.CreatePage(section, newPageName, showOneNote); - Main.ForceReQuery(); - - if(showOneNote) - WindowHelper.FocusOneNote(); - - return showOneNote; - }, + Action = c => CreateNewItem(section, newPageName, true, c, OneNoteApplication.CreatePage), }; } - public Result CreateNewSectionResult(string newSectionName, IOneNoteItem parent) + public Result CreateNewSectionResult(string newSectionName, INotebookOrSectionGroup parent) { newSectionName = newSectionName.Trim(); bool validTitle = OneNoteApplication.IsSectionNameValid(newSectionName); @@ -268,35 +281,11 @@ public Result CreateNewSectionResult(string newSectionName, IOneNoteItem parent) : $"Section names cannot contain: {string.Join(' ', OneNoteApplication.InvalidSectionChars)}", AutoCompleteText = $"{GetAutoCompleteText(parent)}{newSectionName}", IcoPath = iconProvider.NewSection, - Action = c => - { - if (!validTitle) - { - return false; - } - - bool showOneNote = !c.SpecialKeyState.CtrlPressed; - - switch (parent) - { - case OneNoteNotebook notebook: - OneNoteApplication.CreateSection(notebook, newSectionName, showOneNote); - break; - case OneNoteSectionGroup sectionGroup: - OneNoteApplication.CreateSection(sectionGroup, newSectionName, showOneNote); - break; - } - - Main.ForceReQuery(); - if(showOneNote) - WindowHelper.FocusOneNote(); - - return showOneNote; - }, + Action = c => CreateNewItem(parent, newSectionName, validTitle, c, OneNoteApplication.CreateSection), }; } - public Result CreateNewSectionGroupResult(string newSectionGroupName, IOneNoteItem parent) + public Result CreateNewSectionGroupResult(string newSectionGroupName, INotebookOrSectionGroup parent) { newSectionGroupName = newSectionGroupName.Trim(); bool validTitle = OneNoteApplication.IsSectionGroupNameValid(newSectionGroupName); @@ -309,34 +298,10 @@ public Result CreateNewSectionGroupResult(string newSectionGroupName, IOneNoteIt : $"Section group names cannot contain: {string.Join(' ', OneNoteApplication.InvalidSectionGroupChars)}", AutoCompleteText = $"{GetAutoCompleteText(parent)}{newSectionGroupName}", IcoPath = iconProvider.NewSectionGroup, - Action = c => - { - if (!validTitle) - { - return false; - } - - bool showOneNote = !c.SpecialKeyState.CtrlPressed; - - switch (parent) - { - case OneNoteNotebook notebook: - OneNoteApplication.CreateSectionGroup(notebook, newSectionGroupName, showOneNote); - break; - case OneNoteSectionGroup sectionGroup: - OneNoteApplication.CreateSectionGroup(sectionGroup, newSectionGroupName, showOneNote); - break; - } - - Main.ForceReQuery(); - if(showOneNote) - WindowHelper.FocusOneNote(); - - return showOneNote; - }, + Action = c => CreateNewItem(parent, newSectionGroupName, validTitle, c, OneNoteApplication.CreateSectionGroup), }; } - + public Result CreateNewNotebookResult(string newNotebookName) { newNotebookName = newNotebookName.Trim(); @@ -350,23 +315,8 @@ public Result CreateNewNotebookResult(string newNotebookName) : $"Notebook names cannot contain: {string.Join(' ', OneNoteApplication.InvalidNotebookChars)}", AutoCompleteText = $"{ActionKeyword} {settings.Keywords.NotebookExplorer}{newNotebookName}", IcoPath = iconProvider.NewNotebook, - Action = c => - { - if (!validTitle) - { - return false; - } - - bool showOneNote = !c.SpecialKeyState.CtrlPressed; - - OneNoteApplication.CreateNotebook(newNotebookName, showOneNote); - Main.ForceReQuery(); - - if (showOneNote) - WindowHelper.FocusOneNote(); - - return showOneNote; - }, + Action = c => CreateNewItem(null, newNotebookName, validTitle, + c, (_, name, valid) => OneNoteApplication.CreateNotebook(name, valid)), }; } @@ -378,12 +328,17 @@ public List ContextMenu(Result selectedResult) var result = CreateOneNoteItemResult(item, false); result.Title = $"Open and sync \"{item.Name}\""; result.SubTitle = string.Empty; + result.Score = 30; + result.AddSelectedCount = false; result.ContextData = null; results.Add(result); + results.Add(new Result { Title = "Open in new OneNote window", IcoPath = IconProvider.Logo, + Score = 20, + AddSelectedCount = false, Action = _ => { OneNoteApplication.ComObject.NavigateTo(item.ID, fNewWindow: true); @@ -392,21 +347,21 @@ public List ContextMenu(Result selectedResult) } }); - if (item is not OneNotePage) + string autoCompleteText = GetAutoCompleteText(item); + results.Add(new Result { - results.Add(new Result + Title = "Show in Notebook Explorer", + SubTitle = autoCompleteText, + AddSelectedCount = false, + Score = 10, + IcoPath = iconProvider.NotebookExplorer, + Action = _ => { - Title = "Copy Notebook Explorer path to clipboard", - SubTitle = result.AutoCompleteText, - Score = - 1000, - IcoPath = iconProvider.NotebookExplorer, - Action = _ => - { - context.API.CopyToClipboard(result.AutoCompleteText); - return false; - } - }); - } + context.API.BackToQueryResults(); + context.API.ChangeQuery(autoCompleteText, true); + return false; + } + }); } return results; } @@ -444,8 +399,8 @@ Result EmptyCollectionResult(string title, string iconPath, string? subTitle = n } } - private Lazy GetNewPagePreviewPanel(OneNoteSection? section, string? pageTitle) => - new(() => new NewOneNotePagePreviewPanel(context, section, pageTitle)); + private Lazy GetNewPagePreviewPanel(OneNoteSection? section, string? pageTitle) + => new(() => new NewOneNotePagePreviewPanel(context, section, pageTitle)); public static List NoMatchesFound() { @@ -461,9 +416,13 @@ public List InvalidQuery(bool includeSubtitle = true) : string.Empty, iconProvider.Warning); } - public List SearchingByTitle() + public List SearchType(string title, string? parentName) { - return SingleResult("Now searching by title.", string.Empty, iconProvider.Search); + if (!string.IsNullOrWhiteSpace(parentName)) + { + title += $" in \"{parentName}\""; + } + return SingleResult(title, string.Empty, iconProvider.Search); } private static List SingleResult(string title, string subTitle, string iconPath) diff --git a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs index 4d123ca..8c11e2c 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs @@ -118,9 +118,9 @@ private List Explorer(string search, IOneNoteItem? parent, IEnumerable Date: Sun, 27 Jul 2025 21:57:04 +0100 Subject: [PATCH 09/29] Small tweaks and code cleanup Reduce compiler warnings. Improve nomenclature --- .../Icons/IconGeneratorInfo.cs | 18 ++++---- .../Icons/IconProvider.cs | 44 +++++++++---------- Flow.Launcher.Plugin.OneNote/Keywords.cs | 2 +- Flow.Launcher.Plugin.OneNote/Main.cs | 23 +++++----- .../Search/NotebookExplorer.cs | 12 ++--- .../Search/RecentPages.cs | 2 +- .../Search/SearchBase.cs | 4 +- .../Search/SearchExtensions.cs | 2 +- .../Search/SearchManager.cs | 6 +-- .../Search/TitleSearch.cs | 9 ++-- Flow.Launcher.Plugin.OneNote/Settings.cs | 2 +- Flow.Launcher.Plugin.OneNote/UI/Model.cs | 2 +- .../UI/ViewModels/NewOneNotePageViewModel.cs | 8 ++-- .../UI/ViewModels/SettingsViewModel.cs | 2 +- .../UI/Views/ChangeKeywordWindow.xaml.cs | 3 +- .../Views/NewOneNotePagePreviewPanel.xaml.cs | 2 +- Flow.Launcher.Plugin.OneNote/WindowHelper.cs | 4 +- 17 files changed, 69 insertions(+), 76 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs b/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs index 57214e4..32a926f 100644 --- a/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs +++ b/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs @@ -3,28 +3,28 @@ namespace Flow.Launcher.Plugin.OneNote.Icons { - public record struct IconGeneratorInfo + public struct IconGeneratorInfo { - public string Prefix { get; } - public Color? Color { get; } + public readonly string prefix = string.Empty; + public readonly Color? color; public IconGeneratorInfo(IOneNoteItem item) { switch (item) { case OneNoteNotebook n: - Prefix = IconConstants.Notebook; - Color = n.Color; + prefix = IconConstants.Notebook; + color = n.Color; break; case OneNoteSectionGroup sg: - Prefix = sg.IsRecycleBin ? IconConstants.RecycleBin : IconConstants.SectionGroup; + prefix = sg.IsRecycleBin ? IconConstants.RecycleBin : IconConstants.SectionGroup; break; case OneNoteSection s: - Prefix = IconConstants.Section; - Color = s.Color; + prefix = IconConstants.Section; + color = s.Color; break; case OneNotePage: - Prefix = IconConstants.Page; + prefix = IconConstants.Page; break; } } diff --git a/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs b/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs index 2e12698..d29316a 100644 --- a/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs +++ b/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs @@ -12,6 +12,11 @@ namespace Flow.Launcher.Plugin.OneNote.Icons { public class IconProvider : BaseModel { + private readonly PluginInitContext context; + private readonly Settings settings; + private readonly string imagesDirectory; + private readonly ConcurrentDictionary iconCache = new(); + public const string Logo = IC.ImagesDirectory + IC.Logo + ".png"; public string Sync => GetIconPath(IC.Sync); public string Search => GetIconPath(IC.Search); @@ -23,17 +28,12 @@ public class IconProvider : BaseModel public string NewSectionGroup => GetIconPath(IC.NewSectionGroup); public string NewNotebook => GetIconPath(IC.NewNotebook); public string Warning => settings.IconTheme == IconTheme.Color - ? $"{IC.ImagesDirectory}{IC.Warning}.{GetIconThemeString(IconTheme.Dark)}.png" - : GetIconPath(IC.Warning); - - private readonly Settings settings; - private readonly ConcurrentDictionary iconCache = new(); - private readonly string imagesDirectory; + ? $"{IC.ImagesDirectory}{IC.Warning}.{GetIconThemeString(IconTheme.Dark)}.png" + : GetIconPath(IC.Warning); - public DirectoryInfo GeneratedImagesDirectoryInfo { get; } public int CachedIconCount => iconCache.Keys.Count(k => char.IsDigit(k.Split('.')[1][1])); - - private readonly PluginInitContext context; + public DirectoryInfo GeneratedImagesDirectoryInfo { get; } + public IconProvider(PluginInitContext context, Settings settings) { @@ -60,7 +60,7 @@ private static string GetIconThemeString(IconTheme iconTheme) { iconTheme = FlowLauncherThemeToIconTheme(); } - return Enum.GetName(iconTheme).ToLower(); + return iconTheme.ToString().ToLower(); } private static IconTheme FlowLauncherThemeToIconTheme() @@ -79,38 +79,38 @@ private static IconTheme FlowLauncherThemeToIconTheme() public Result.IconDelegate GetIcon(IconGeneratorInfo info) { - bool generate = (string.CompareOrdinal(info.Prefix, IC.Notebook) == 0 - || string.CompareOrdinal(info.Prefix, IC.Section) == 0) + bool generate = (string.CompareOrdinal(info.prefix, IC.Notebook) == 0 + || string.CompareOrdinal(info.prefix, IC.Section) == 0) && settings.CreateColoredIcons - && info.Color.HasValue; + && info.color.HasValue; return generate ? GetIconGenerated : GetIconStatic; ImageSource GetIconGenerated() { - var imageSource = iconCache.GetOrAdd($"{info.Prefix}.{info.Color!.Value.ToArgb()}.png", ImageSourceFactory, info.Color.Value); + var imageSource = iconCache.GetOrAdd($"{info.prefix}.{info.color!.Value.ToArgb()}.png", + static (key, t) => ImageSourceFactory(t.self, key, t.Color), + (Color: info.color.Value, self: this)); OnPropertyChanged(nameof(CachedIconCount)); return imageSource; } ImageSource GetIconStatic() { - return iconCache.GetOrAdd($"{info.Prefix}.{GetIconThemeString(settings.IconTheme)}.png", key => - { - var path = Path.Combine(imagesDirectory, key); - return BitmapImageFromPath(path); - }); + return iconCache.GetOrAdd($"{info.prefix}.{GetIconThemeString(settings.IconTheme)}.png", + static (key,dir) => BitmapImageFromPath(Path.Combine(dir, key)), + imagesDirectory); } } - private ImageSource ImageSourceFactory(string key, Color color) + private static ImageSource ImageSourceFactory(IconProvider self, string key, Color color) { var prefix = key.Split('.')[0]; - var path = Path.Combine(imagesDirectory, $"{prefix}.dark.png"); + var path = Path.Combine(self.imagesDirectory, $"{prefix}.dark.png"); var bitmap = BitmapImageFromPath(path); var newBitmap = ChangeIconColor(bitmap, color); - path = $"{GeneratedImagesDirectoryInfo.FullName}{key}"; + path = $"{self.GeneratedImagesDirectoryInfo.FullName}{key}"; using var fileStream = new FileStream(path, FileMode.Create); var encoder = new PngBitmapEncoder(); diff --git a/Flow.Launcher.Plugin.OneNote/Keywords.cs b/Flow.Launcher.Plugin.OneNote/Keywords.cs index deecd82..5b34f2e 100644 --- a/Flow.Launcher.Plugin.OneNote/Keywords.cs +++ b/Flow.Launcher.Plugin.OneNote/Keywords.cs @@ -39,7 +39,7 @@ public void ChangeKeyword(string newValue) public class KeywordJsonConverter : JsonConverter { public override Keyword Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - => new(JsonSerializer.Deserialize(ref reader, options)); + => new(JsonSerializer.Deserialize(ref reader, options)!); public override void Write(Utf8JsonWriter writer, Keyword value, JsonSerializerOptions options) => JsonSerializer.Serialize(writer, value.Value, options); diff --git a/Flow.Launcher.Plugin.OneNote/Main.cs b/Flow.Launcher.Plugin.OneNote/Main.cs index 5030ca6..1830843 100644 --- a/Flow.Launcher.Plugin.OneNote/Main.cs +++ b/Flow.Launcher.Plugin.OneNote/Main.cs @@ -20,10 +20,8 @@ public class Main : IAsyncPlugin, IContextMenu, ISettingProvider, IDisposable private IconProvider iconProvider; private static SemaphoreSlim semaphore; - private static Main instance; - - private Query currentQuery; + public Task InitAsync(PluginInitContext context) { this.context = context; @@ -34,7 +32,6 @@ public Task InitAsync(PluginInitContext context) searchManager = new SearchManager(context, settings, resultCreator); semaphore = new SemaphoreSlim(1,1); context.API.VisibilityChanged += OnVisibilityChanged; - instance = this; return Task.CompletedTask; } @@ -46,19 +43,26 @@ private void OnVisibilityChanged(object _, VisibilityChangedEventArgs e) } } - private static async Task OneNoteInitAsync(CancellationToken token = default) + private static async Task OneNoteInitAsync(CancellationToken token) { if (OneNoteApplication.HasComObject) return; - if (await semaphore.WaitAsync(0,token)) + if (!await semaphore.WaitAsync(0,token)) + return; + + try + { OneNoteApplication.InitComObject(); + } + finally + { + semaphore.Release(); + } - semaphore.Release(); } public async Task> QueryAsync(Query query, CancellationToken token) { - currentQuery = query; Task init = OneNoteInitAsync(token); if (string.IsNullOrEmpty(query.Search)) @@ -69,9 +73,6 @@ public async Task> QueryAsync(Query query, CancellationToken token) return searchManager.Query(query.Search); } - [Obsolete("Use PluginInitContext.API.ReQuery")] - public static void ForceReQuery() => instance.context.API.ChangeQuery(instance.currentQuery.RawQuery, true); - public List LoadContextMenus(Result selectedResult) { return resultCreator.ContextMenu(selectedResult); diff --git a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs index 8c11e2c..5290575 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs @@ -33,13 +33,7 @@ public override List GetResults(string query) Result result = resultCreator.CreateOneNoteItemResult(parent, false, score: Result.MaxScore); result.Title = $"Open \"{parent.Name}\" in OneNote"; - result.SubTitle = search switch - { - { } when search.StartsWithOrd(Keywords.TitleSearch) => $"Now searching by title in \"{parent.Name}\"", - { } when search.StartsWithOrd(Keywords.ScopedSearch) => $"Now searching all pages in \"{parent.Name}\"", - _ => $"Use \'{Keywords.ScopedSearch}\' to search this item. Use \'{Keywords.TitleSearch}\' to search by title in this item", - }; - + result.SubTitle = $"Use \'{Keywords.ScopedSearch}\' to search this item. Use \'{Keywords.TitleSearch}\' to search by title in this item"; results.Add(result); return results; } @@ -84,7 +78,7 @@ private List ShowAll(IOneNoteItem? parent, IEnumerable col private List ScopedSearch(string query, IOneNoteItem parent) { if (query.Length == Keywords.ScopedSearch.Length) - return new List(0); + return resultCreator.SearchType("Now searching all pages", parent.Name); if (!char.IsLetterOrDigit(query[Keywords.ScopedSearch.Length])) return resultCreator.InvalidQuery(); @@ -102,7 +96,7 @@ private List Explorer(string search, IOneNoteItem? parent, IEnumerable resultCreator.CreateOneNoteItemResult(r.item, true, r.highlightData, r.score)) + .Select(r => resultCreator.CreateOneNoteItemResult(r.Item, true, r.HighlightData, r.Score)) .ToList(); // If parent is a section, pages inside can have the same name diff --git a/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs b/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs index 24442c5..e408db1 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs @@ -13,7 +13,7 @@ public override List GetResults(string query) { int count = settings.DefaultRecentsCount; - if (query.Length > Keyword.Length && int.TryParse(query[Keyword.Length..], out int userChosenCount)) + if (query.Length > keyword.Length && int.TryParse(query[keyword.Length..], out int userChosenCount)) count = userChosenCount; return OneNoteApplication.GetNotebooks() diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs index ac7ae69..794681e 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs @@ -7,13 +7,13 @@ public abstract class SearchBase protected readonly PluginInitContext context; protected readonly Settings settings; protected readonly ResultCreator resultCreator; - public readonly Keyword Keyword; + public readonly Keyword keyword; protected SearchBase(PluginInitContext context, Settings settings, ResultCreator resultCreator, Keyword keyword) { this.context = context; this.settings = settings; this.resultCreator = resultCreator; - Keyword = keyword; + this.keyword = keyword; } protected Keywords Keywords => settings.Keywords; public abstract List GetResults(string query); diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs index 53a7806..937a888 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs @@ -5,7 +5,7 @@ namespace Flow.Launcher.Plugin.OneNote.Search { - public record struct SearchResult(T item, List? highlightData, int score) where T : IOneNoteItem; + public record struct SearchResult(T Item, List? HighlightData, int Score) where T : IOneNoteItem; public static class SearchExtensions { public static IEnumerable> FuzzySearch(this IEnumerable source, string search, PluginInitContext context) where T: IOneNoteItem diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs index 07fc3e7..8c8c7cf 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs @@ -22,9 +22,9 @@ public List Query(string search) { return search switch { - { } when search.StartsWithOrd(titleSearch.Keyword) => titleSearch.GetResults(search), - { } when search.StartsWithOrd(notebookExplorer.Keyword) => notebookExplorer.GetResults(search), - { } when search.StartsWithOrd(recentPages.Keyword) => recentPages.GetResults(search), + { } when search.StartsWithOrd(titleSearch.keyword) => titleSearch.GetResults(search), + { } when search.StartsWithOrd(notebookExplorer.keyword) => notebookExplorer.GetResults(search), + { } when search.StartsWithOrd(recentPages.keyword) => recentPages.GetResults(search), _ => defaultSearch.GetResults(search!), }; } diff --git a/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs index b4e08aa..a9f9248 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Odotocodot.OneNote.Linq; @@ -14,15 +13,15 @@ public TitleSearch(PluginInitContext context, Settings settings, ResultCreator r public List Filter(string query, IOneNoteItem? parent, IEnumerable collection) { - if (query.Length == Keyword.Length || parent == null) - return resultCreator.SearchingByTitle(); + if (query.Length == keyword.Length) + return resultCreator.SearchType("Now searching by title", parent?.Name); - var currentSearch = query[Keyword.Length..]; + var currentSearch = query[keyword.Length..]; var results = collection.Traverse() .FilterBySettings(settings) .FuzzySearch(currentSearch, context) - .Select(x => resultCreator.CreateOneNoteItemResult(x.item, false, x.highlightData, x.score)) + .Select(x => resultCreator.CreateOneNoteItemResult(x.Item, false, x.HighlightData, x.Score)) .ToList(); return results.Any() ? results : ResultCreator.NoMatchesFound(); diff --git a/Flow.Launcher.Plugin.OneNote/Settings.cs b/Flow.Launcher.Plugin.OneNote/Settings.cs index 86a4876..109321d 100644 --- a/Flow.Launcher.Plugin.OneNote/Settings.cs +++ b/Flow.Launcher.Plugin.OneNote/Settings.cs @@ -7,7 +7,7 @@ public class Settings : UI.Model private bool showUnread = true; private int defaultRecentsCount = 5; private bool showRecycleBin = true; - private bool showEncrypted = false; + private bool showEncrypted; private bool createColoredIcons = true; private IconTheme iconTheme = IconTheme.Color; public Keywords Keywords { get; init; } = new Keywords(); diff --git a/Flow.Launcher.Plugin.OneNote/UI/Model.cs b/Flow.Launcher.Plugin.OneNote/UI/Model.cs index 1d9845b..71699e8 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/Model.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/Model.cs @@ -4,7 +4,7 @@ namespace Flow.Launcher.Plugin.OneNote.UI { public class Model : BaseModel { - protected bool SetProperty(ref T field, T newValue, [CallerMemberName] string propertyName = null) + protected bool SetProperty(ref T field, T newValue, [CallerMemberName] string? propertyName = null) { if (Equals(field, newValue)) return false; diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs index 8ca7a01..0e93d81 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs @@ -7,12 +7,12 @@ namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels { public class NewOneNotePageViewModel : Model { - private string pageTitle = string.Empty; + private string? pageTitle = string.Empty; private string pageContent = string.Empty; - private readonly OneNoteSection section; + private readonly OneNoteSection? section; private readonly PluginInitContext context; - public NewOneNotePageViewModel(PluginInitContext context, OneNoteSection section, string pageTitle) + public NewOneNotePageViewModel(PluginInitContext context, OneNoteSection? section, string? pageTitle) { this.context = context; this.section = section; @@ -44,7 +44,7 @@ private void CreatePage(bool openImmediately) } } - public string PageTitle + public string? PageTitle { get => pageTitle; set => SetProperty(ref pageTitle, value); diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs index f1e0898..0c4d27d 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs @@ -76,7 +76,7 @@ public KeywordViewModel? SelectedKeyword //quick and dirty non MVVM stuffs private async Task ClearCachedIcons() { - var dialog = new Modern.ContentDialog() + var dialog = new Modern.ContentDialog { Title = "Clear Cached Icons", Content = $"Delete cached notebook and sections icons.\n" + diff --git a/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml.cs b/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml.cs index 88054a6..6512ccd 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml.cs @@ -1,5 +1,4 @@ -using System.Windows; -using Flow.Launcher.Plugin.OneNote.UI.ViewModels; +using Flow.Launcher.Plugin.OneNote.UI.ViewModels; namespace Flow.Launcher.Plugin.OneNote.UI.Views { diff --git a/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml.cs b/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml.cs index 7a66ea2..97c62de 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml.cs @@ -8,7 +8,7 @@ namespace Flow.Launcher.Plugin.OneNote.UI.Views { public partial class NewOneNotePagePreviewPanel : UserControl { - public NewOneNotePagePreviewPanel(PluginInitContext context, OneNoteSection section, string pageTitle) + public NewOneNotePagePreviewPanel(PluginInitContext context, OneNoteSection? section, string? pageTitle) { DataContext = new NewOneNotePageViewModel(context, section, pageTitle); InitializeComponent(); diff --git a/Flow.Launcher.Plugin.OneNote/WindowHelper.cs b/Flow.Launcher.Plugin.OneNote/WindowHelper.cs index d186520..86c9b92 100644 --- a/Flow.Launcher.Plugin.OneNote/WindowHelper.cs +++ b/Flow.Launcher.Plugin.OneNote/WindowHelper.cs @@ -16,10 +16,10 @@ public static void FocusOneNote() IntPtr handle = process.MainWindowHandle; if (IsIconic(handle)) { - ShowWindow(handle, SW_RESTORE); + _ = ShowWindow(handle, SW_RESTORE); } - SetForegroundWindow(handle); + _ = SetForegroundWindow(handle); } private const int SW_RESTORE = 9; From 423621ded813cac51d3e98c7eee5fafd4bc37659 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Fri, 1 Aug 2025 22:52:22 +0100 Subject: [PATCH 10/29] Update keyword changing logic --- Flow.Launcher.Plugin.OneNote/Keywords.cs | 15 ++++-------- .../Search/NotebookExplorer.cs | 6 ++--- .../UI/RelayCommand.cs | 7 ++---- .../UI/ViewModels/ChangeKeywordViewModel.cs | 6 ++--- .../UI/ViewModels/KeywordViewModel.cs | 23 +++++++++++-------- .../UI/ViewModels/SettingsViewModel.cs | 4 +++- .../UI/Views/ChangeKeywordWindow.xaml | 2 +- .../UI/Views/SettingsView.xaml | 8 +++---- 8 files changed, 33 insertions(+), 38 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/Keywords.cs b/Flow.Launcher.Plugin.OneNote/Keywords.cs index 5b34f2e..bc06637 100644 --- a/Flow.Launcher.Plugin.OneNote/Keywords.cs +++ b/Flow.Launcher.Plugin.OneNote/Keywords.cs @@ -16,22 +16,15 @@ public class Keywords } [JsonConverter(typeof(KeywordJsonConverter))] - public class Keyword : BaseModel + public class Keyword { - public Keyword(string value) - { - Value = value; - } + public Keyword(string value) => Value = value; public string Value { get; private set; } - public void ChangeKeyword(string newValue) - { - Value = newValue; - OnPropertyChanged(nameof(Value)); - } + public void ChangeKeyword(string newValue) => Value = newValue; public int Length => Value.Length; - public static implicit operator string(Keyword keyword) => keyword.Value; + public static implicit operator string(Keyword keyword) => keyword.Value; public override string ToString() => Value; } diff --git a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs index 5290575..a53125f 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs @@ -112,9 +112,9 @@ private List Explorer(string search, IOneNoteItem? parent, IEnumerable execute, Predicate? canExecute = null) this.execute = execute; this.canExecute = canExecute; } - - public bool CanExecute(T? parameter) => canExecute?.Invoke(parameter) != false; - public void Execute(T? parameter) => execute(parameter); - public bool CanExecute(object? parameter) => CanExecute((T?)parameter); - public void Execute(object? parameter) => Execute((T?)parameter); + public bool CanExecute(object? parameter) => canExecute?.Invoke((T?)parameter) != false; + public void Execute(object? parameter) => execute.Invoke((T?)parameter); } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs index 5901b0d..c502a71 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs @@ -43,13 +43,13 @@ private bool CanChangeKeyword(string? newKeyword) } newKeyword = newKeyword.Trim(); - if (SelectedKeyword.Keyword == newKeyword) + if (SelectedKeyword.Value == newKeyword) { ErrorMessage = "The new keyword is the same as the old keyword."; return false; } - KeywordViewModel? alreadySetKeyword = keywords.FirstOrDefault(k => k.Keyword == newKeyword); + KeywordViewModel? alreadySetKeyword = keywords.FirstOrDefault(k => k.Value == newKeyword); if (alreadySetKeyword != null) { ErrorMessage = $"The new keyword is already set for {alreadySetKeyword.Name}."; @@ -62,7 +62,7 @@ private bool CanChangeKeyword(string? newKeyword) private void ChangeKeyword(string newKeyword) { - SelectedKeyword.Keyword.ChangeKeyword(newKeyword.Trim()); + SelectedKeyword.Value = newKeyword.Trim(); context.API.SaveSettingJsonStorage(); context.API.ReQuery(); closeAction.Invoke(); diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs index 28b5d6d..24dfb8b 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs @@ -1,20 +1,23 @@ namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels { - public class KeywordViewModel : BaseModel + public class KeywordViewModel : Model { + private readonly Keyword keyword; public KeywordViewModel(string keywordName, Keyword keyword) { Name = keywordName; - Keyword = keyword; - keyword.PropertyChanged += (_, args) => - { - if (args.PropertyName == nameof(Keyword.Value)) - { - OnPropertyChanged(nameof(Keyword)); - } - }; + this.keyword = keyword; } public string Name { get; } - public Keyword Keyword { get; } + + public string Value + { + get => keyword.Value; + set + { + keyword.ChangeKeyword(value); + OnPropertyChanged(); + } + } } } diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs index 0c4d27d..ff226a2 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs @@ -18,7 +18,7 @@ public SettingsViewModel(PluginInitContext context, Settings settings, IconProvi { this.iconProvider = iconProvider; Settings = settings; - Keywords = settings.Keywords + Keywords = settings.Keywords //Order is the order they are written in Keywords.cs .GetType() .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Select(p => new KeywordViewModel(p.Name.Humanize(LetterCasing.Title), (Keyword)p.GetValue(settings.Keywords)!)) @@ -60,6 +60,8 @@ public SettingsViewModel(PluginInitContext context, Settings settings, IconProvi public ICommand ClearCachedIconsCommand { get; } public Settings Settings { get; } public KeywordViewModel[] Keywords { get; } + public KeywordViewModel NotebookExplorerKeyword => Keywords[0]; + public KeywordViewModel RecentPagesKeyword => Keywords[1]; public IconThemeViewModel[] IconThemes { get; } public string CachedIconsFileSize => iconProvider.GeneratedImagesDirectoryInfo.EnumerateFiles() .Select(file => file.Length) diff --git a/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml b/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml index 8c172f1..ba6515a 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml +++ b/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml @@ -117,7 +117,7 @@ FontSize="14" FontWeight="SemiBold" Foreground="{DynamicResource Color05B}" - Text="{Binding SelectedKeyword.Keyword}" /> + Text="{Binding SelectedKeyword.Value}" /> - + - + - + - + From 5429742badd5a8c089c6de8b8727cd55a471278b Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sat, 4 Oct 2025 13:14:51 +0100 Subject: [PATCH 11/29] Update Flow.Launcher.Plugin to 5.0.0 Update to Net 9.0 --- Changelog.md | 7 +++++++ .../Flow.Launcher.Plugin.OneNote.csproj | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 32ddd3c..2a28697 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,12 @@ # Changelog +## 3.0.0 - 2025-#-## +- **⚠ Now requires Flow Launcher version 2.0.0 or later.** +- Update Flow.Launcher.Plugin to 5.0.0 +- Update to Net 9.0 +- Refactored code + + ## 2.1.2 - 2025-6-11 - Added the ability to open items in a new OneNote window using the context menu.([#28](https://github.com/Odotocodot/Flow.Launcher.Plugin.OneNote/issues/28)) diff --git a/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj b/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj index 96cf22c..e44d482 100644 --- a/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj +++ b/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj @@ -1,7 +1,7 @@  - net7.0-windows + net9.0-windows Flow.Launcher.Plugin.OneNote Flow.Launcher.Plugin.OneNote Odotocodot @@ -28,7 +28,7 @@ - + From 86943873a3e8be58c1fe8a1bb4bf588eca83b049 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sat, 3 Jan 2026 15:50:08 +0000 Subject: [PATCH 12/29] migrate to `LinqToOneNote` [1/2] --- Changelog.md | 3 ++- .../Flow.Launcher.Plugin.OneNote.csproj | 2 +- .../Icons/IconGeneratorInfo.cs | 10 +++++----- Flow.Launcher.Plugin.OneNote/Main.cs | 10 +++++----- .../Search/DefaultSearch.cs | 8 ++++---- .../Search/NotebookExplorer.cs | 20 ++++++++++--------- .../Search/RecentPages.cs | 18 +++++++++-------- .../Search/SearchExtensions.cs | 4 ++-- .../Search/TitleSearch.cs | 7 ++++--- .../UI/ViewModels/NewOneNotePageViewModel.cs | 14 ++++++------- .../Views/NewOneNotePagePreviewPanel.xaml.cs | 4 ++-- 11 files changed, 53 insertions(+), 47 deletions(-) diff --git a/Changelog.md b/Changelog.md index 2a28697..9228a5f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,10 +1,11 @@ # Changelog -## 3.0.0 - 2025-#-## +## 3.0.0 - 2026-#-## - **⚠ Now requires Flow Launcher version 2.0.0 or later.** - Update Flow.Launcher.Plugin to 5.0.0 - Update to Net 9.0 - Refactored code +- Migrate from `Odotocodot.OneNote.Linq 1.2.0` to `LinqToOneNote 2.0.0` ## 2.1.2 - 2025-6-11 diff --git a/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj b/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj index e44d482..a048bd3 100644 --- a/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj +++ b/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj @@ -31,7 +31,7 @@ - + diff --git a/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs b/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs index 32a926f..fdb9d41 100644 --- a/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs +++ b/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs @@ -1,5 +1,5 @@ using System.Drawing; -using Odotocodot.OneNote.Linq; +using LinqToOneNote; namespace Flow.Launcher.Plugin.OneNote.Icons { @@ -12,18 +12,18 @@ public IconGeneratorInfo(IOneNoteItem item) { switch (item) { - case OneNoteNotebook n: + case Notebook n: prefix = IconConstants.Notebook; color = n.Color; break; - case OneNoteSectionGroup sg: + case SectionGroup sg: prefix = sg.IsRecycleBin ? IconConstants.RecycleBin : IconConstants.SectionGroup; break; - case OneNoteSection s: + case Section s: prefix = IconConstants.Section; color = s.Color; break; - case OneNotePage: + case Page: prefix = IconConstants.Page; break; } diff --git a/Flow.Launcher.Plugin.OneNote/Main.cs b/Flow.Launcher.Plugin.OneNote/Main.cs index 1830843..95752a0 100644 --- a/Flow.Launcher.Plugin.OneNote/Main.cs +++ b/Flow.Launcher.Plugin.OneNote/Main.cs @@ -6,7 +6,7 @@ using Flow.Launcher.Plugin.OneNote.Icons; using Flow.Launcher.Plugin.OneNote.Search; using Flow.Launcher.Plugin.OneNote.UI.Views; -using Odotocodot.OneNote.Linq; +using OneNoteApp = LinqToOneNote.OneNote; namespace Flow.Launcher.Plugin.OneNote { #nullable disable @@ -39,13 +39,13 @@ private void OnVisibilityChanged(object _, VisibilityChangedEventArgs e) { if (context.CurrentPluginMetadata.Disabled || !e.IsVisible) { - Task.Run(OneNoteApplication.ReleaseComObject); + Task.Run(OneNoteApp.ReleaseComObject); } } private static async Task OneNoteInitAsync(CancellationToken token) { - if (OneNoteApplication.HasComObject) + if (OneNoteApp.HasComObject) return; if (!await semaphore.WaitAsync(0,token)) @@ -53,7 +53,7 @@ private static async Task OneNoteInitAsync(CancellationToken token) try { - OneNoteApplication.InitComObject(); + OneNoteApp.InitComObject(); } finally { @@ -87,7 +87,7 @@ public void Dispose() { context.API.VisibilityChanged -= OnVisibilityChanged; semaphore.Dispose(); - OneNoteApplication.ReleaseComObject(); + OneNoteApp.ReleaseComObject(); } } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs b/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs index ce0e2d5..0862435 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using Odotocodot.OneNote.Linq; +using OneNoteApp = LinqToOneNote.OneNote; namespace Flow.Launcher.Plugin.OneNote.Search { @@ -16,9 +16,9 @@ public override List GetResults(string query) return resultCreator.InvalidQuery(); } - return OneNoteApplication.FindPages(query) - .Select(pg => resultCreator.CreatePageResult(pg, query)) - .ToList(); + return OneNoteApp.FindPages(query) + .Select(pg => resultCreator.CreatePageResult(pg, query)) + .ToList(); } } diff --git a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs index a53125f..e2a175d 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; -using Odotocodot.OneNote.Linq; -using Odotocodot.OneNote.Linq.Abstractions; +using LinqToOneNote; +using LinqToOneNote.Abstractions; +using OneNoteApp = LinqToOneNote.OneNote; + namespace Flow.Launcher.Plugin.OneNote.Search { @@ -22,7 +24,7 @@ public override List GetResults(string query) List results = search switch { - { } when search.StartsWithOrd(Keywords.TitleSearch) && parent is not OneNotePage => titleSearch.Filter(search, parent, collection), + { } when search.StartsWithOrd(Keywords.TitleSearch) && parent is not Page => titleSearch.Filter(search, parent, collection), { } when search.StartsWithOrd(Keywords.ScopedSearch) && parent is INotebookOrSectionGroup => ScopedSearch(search, parent), { } when !string.IsNullOrWhiteSpace(search) => Explorer(search, parent, collection), _ => ShowAll(parent, collection), @@ -42,7 +44,7 @@ private bool ValidateSearch(string query, out string? lastSearch, out IOneNoteIt { lastSearch = null; parent = null; - collection = OneNoteApplication.GetNotebooks(); + collection = OneNoteApp.GetFullHierarchy().Notebooks; string search = query[(query.IndexOf(Keywords.NotebookExplorer, StringComparison.Ordinal) + Keywords.NotebookExplorer.Length)..]; const string separator = Keywords.NotebookExplorerSeparator; @@ -85,9 +87,9 @@ private List ScopedSearch(string query, IOneNoteItem parent) string currentSearch = query[Keywords.TitleSearch.Length..]; - var results = OneNoteApplication.FindPages(currentSearch, parent) - .Select(pg => resultCreator.CreatePageResult(pg, currentSearch)) - .ToList(); + var results = OneNoteApp.FindPages(currentSearch, parent) + .Select(pg => resultCreator.CreatePageResult(pg, currentSearch)) + .ToList(); return results.Any() ? results : ResultCreator.NoMatchesFound(); } @@ -100,7 +102,7 @@ private List Explorer(string search, IOneNoteItem? parent, IEnumerable string.Equals(search.Trim(), result.Title, StringComparison.OrdinalIgnoreCase))) + if (parent is not Section && results.Any(result => string.Equals(search.Trim(), result.Title, StringComparison.OrdinalIgnoreCase))) return results; if (parent?.IsInRecycleBin() == true) @@ -116,7 +118,7 @@ private List Explorer(string search, IOneNoteItem? parent, IEnumerable GetResults(string query) if (query.Length > keyword.Length && int.TryParse(query[keyword.Length..], out int userChosenCount)) count = userChosenCount; - return OneNoteApplication.GetNotebooks() - .GetPages() - .FilterBySettings(settings) - .OrderByDescending(pg => pg.LastModified) - .Take(count) - .Select(resultCreator.CreateRecentPageResult) - .ToList(); + return OneNoteApp.GetFullHierarchy() + .Notebooks + .GetAllPages() + .FilterBySettings(settings) + .OrderByDescending(pg => pg.LastModified) + .Take(count) + .Select(resultCreator.CreateRecentPageResult) + .ToList(); } } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs index 937a888..e7abd29 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using Flow.Launcher.Plugin.SharedModels; -using Odotocodot.OneNote.Linq; +using LinqToOneNote; namespace Flow.Launcher.Plugin.OneNote.Search { @@ -24,7 +24,7 @@ public static IEnumerable FilterBySettings(this IEnumerable source, Set foreach (var item in source) { var success = true; - if (settings.ShowEncrypted && item is OneNoteSection section) + if (settings.ShowEncrypted && item is Section section) { success = !section.Encrypted; } diff --git a/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs index a9f9248..2431392 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; -using Odotocodot.OneNote.Linq; +using LinqToOneNote; +using OneNoteApp = LinqToOneNote.OneNote; namespace Flow.Launcher.Plugin.OneNote.Search { @@ -9,7 +10,7 @@ public class TitleSearch : SearchBase public TitleSearch(PluginInitContext context, Settings settings, ResultCreator resultCreator) : base(context, settings, resultCreator, settings.Keywords.TitleSearch) { } - public override List GetResults(string query) => Filter(query, null, OneNoteApplication.GetNotebooks()); + public override List GetResults(string query) => Filter(query, null, OneNoteApp.GetFullHierarchy().Notebooks); public List Filter(string query, IOneNoteItem? parent, IEnumerable collection) { @@ -18,7 +19,7 @@ public List Filter(string query, IOneNoteItem? parent, IEnumerable resultCreator.CreateOneNoteItemResult(x.Item, false, x.HighlightData, x.Score)) diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs index 0e93d81..cde0577 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs @@ -1,7 +1,8 @@ using System; using System.Windows.Input; using Flow.Launcher.Plugin.OneNote.Icons; -using Odotocodot.OneNote.Linq; +using LinqToOneNote; +using OneNoteApp = LinqToOneNote.OneNote; namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels { @@ -9,10 +10,10 @@ public class NewOneNotePageViewModel : Model { private string? pageTitle = string.Empty; private string pageContent = string.Empty; - private readonly OneNoteSection? section; + private readonly Section? section; private readonly PluginInitContext context; - public NewOneNotePageViewModel(PluginInitContext context, OneNoteSection? section, string? pageTitle) + public NewOneNotePageViewModel(PluginInitContext context, Section? section, string? pageTitle) { this.context = context; this.section = section; @@ -23,16 +24,15 @@ public NewOneNotePageViewModel(PluginInitContext context, OneNoteSection? sectio private void CreatePage(bool openImmediately) { - var id = OneNoteApplication.CreatePage(section, PageTitle, false); - var page = (OneNotePage)OneNoteItemExtensions.FindByID(id); + var page = OneNoteApp.CreatePage(section, PageTitle); var pageContentXml = page.GetPageContent(); var xmlWrap = $""; pageContentXml = pageContentXml.Insert(pageContentXml.IndexOf("", StringComparison.Ordinal), xmlWrap); - OneNoteApplication.UpdatePageContent(pageContentXml); + OneNoteApp.UpdatePageContent(pageContentXml); context.API.ReQuery(); if (openImmediately) { - page.OpenInOneNote(); + page.Open(); context.API.HideMainWindow(); WindowHelper.FocusOneNote(); } diff --git a/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml.cs b/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml.cs index 97c62de..4d000ed 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml.cs @@ -2,13 +2,13 @@ using System.Windows.Controls; using System.Windows.Input; using Flow.Launcher.Plugin.OneNote.UI.ViewModels; -using Odotocodot.OneNote.Linq; +using LinqToOneNote; namespace Flow.Launcher.Plugin.OneNote.UI.Views { public partial class NewOneNotePagePreviewPanel : UserControl { - public NewOneNotePagePreviewPanel(PluginInitContext context, OneNoteSection? section, string? pageTitle) + public NewOneNotePagePreviewPanel(PluginInitContext context, Section? section, string? pageTitle) { DataContext = new NewOneNotePageViewModel(context, section, pageTitle); InitializeComponent(); From f683c2e4f89def398e429eeeba9bfdd87a5bdea7 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:39:14 +0000 Subject: [PATCH 13/29] migrate to `LinqToOneNote` [2/2] --- Flow.Launcher.Plugin.OneNote/ResultCreator.cs | 161 +++++++----------- 1 file changed, 63 insertions(+), 98 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs index c8c3579..98ea5eb 100644 --- a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs +++ b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using System.Windows.Controls; using Flow.Launcher.Plugin.OneNote.Icons; using Flow.Launcher.Plugin.OneNote.UI.Views; using Humanizer; -using Odotocodot.OneNote.Linq; -using Odotocodot.OneNote.Linq.Abstractions; +using LinqToOneNote; +using LinqToOneNote.Abstractions; +using OneNoteApp = LinqToOneNote.OneNote; namespace Flow.Launcher.Plugin.OneNote { @@ -28,8 +28,7 @@ public ResultCreator(PluginInitContext context, Settings settings, IconProvider this.context = context; } - private static string GetNicePath(IOneNoteItem item, string separator = PathSeparator) => - item.RelativePath.Replace(OneNoteApplication.RelativePathSeparator.ToString(), separator); + private static string GetNicePath(IOneNoteItem item, string separator = PathSeparator) => item.GetRelativePath(false, separator); private string GetTitle(IOneNoteItem item, List? highlightData) { @@ -51,7 +50,7 @@ private string GetTitle(IOneNoteItem item, List? highlightData) private string GetAutoCompleteText(IOneNoteItem item) //Auto complete text if in notebook explorer { - string slash = item is OneNotePage ? string.Empty : Keywords.NotebookExplorerSeparator; + string slash = item is Page ? string.Empty : Keywords.NotebookExplorerSeparator; return $"{ActionKeyword} {settings.Keywords.NotebookExplorer}{GetNicePath(item, Keywords.NotebookExplorerSeparator)}{slash}"; } @@ -105,7 +104,7 @@ public List EmptyQuery() PreviewPanel = GetNewPagePreviewPanel(null, null), Action = _ => { - OneNoteApplication.CreateQuickNote(true); + OneNoteApp.CreateQuickNote(OpenMode.ExistingOrNewWindow); WindowHelper.FocusOneNote(); return true; }, @@ -118,17 +117,17 @@ public List EmptyQuery() Score = int.MinValue, Action = _ => { - var notebooks = OneNoteApplication.GetNotebooks(); + var notebooks = OneNoteApp.GetFullHierarchy().Notebooks; foreach (var notebook in notebooks) { notebook.Sync(); } - notebooks.GetPages() + notebooks.GetAllPages() .Where(i => !i.IsInRecycleBin) .OrderByDescending(pg => pg.LastModified) .First() - .OpenInOneNote(); + .Open(); WindowHelper.FocusOneNote(); return true; @@ -154,17 +153,17 @@ public Result CreateOneNoteItemResult(IOneNoteItem item, bool actionIsAutoComple {TrianglePoint}{i.LastModified:F} Contains: - {TrianglePoint}{"section group".ToQuantity(i.SectionGroups.Count())} - {TrianglePoint}{"section".ToQuantity(i.Sections.Count())} - {TrianglePoint}{"page".ToQuantity(i.GetPages().Count())} + {TrianglePoint}{"section group".ToQuantity(i.SectionGroups.Count)} + {TrianglePoint}{"section".ToQuantity(i.Sections.Count)} + {TrianglePoint}{"page".ToQuantity(i.GetAllPages().Count())} """; - if (i is OneNoteNotebook) + if (i is Notebook) { subTitle = string.Empty; } break; - case OneNoteSection section: + case Section section: if (section.Encrypted) { title += $" [Encrypted] {(section.Locked ? "[Locked]" : "[Unlocked]")}"; @@ -176,10 +175,10 @@ public Result CreateOneNoteItemResult(IOneNoteItem item, bool actionIsAutoComple {TrianglePoint}{section.LastModified:F} Contains: - {TrianglePoint}{"page".ToQuantity(section.GetPages().Count())} + {TrianglePoint}{"page".ToQuantity(section.GetAllPages().Count())} """; break; - case OneNotePage page: + case Page page: autoCompleteText = actionIsAutoComplete ? autoCompleteText[..^1] : string.Empty; actionIsAutoComplete = false; @@ -214,7 +213,7 @@ public Result CreateOneNoteItemResult(IOneNoteItem item, bool actionIsAutoComple await Task.Run(() => { item.Sync(); - item.OpenInOneNote(); + item.Open(); }); WindowHelper.FocusOneNote(); return true; @@ -222,10 +221,10 @@ await Task.Run(() => }; } - public Result CreatePageResult(OneNotePage page, string query) + public Result CreatePageResult(Page page, string query) => CreateOneNoteItemResult(page, false, string.IsNullOrWhiteSpace(query) ? null : context.API.FuzzySearch(query, page.Name).MatchData); - public Result CreateRecentPageResult(OneNotePage page) + public Result CreateRecentPageResult(Page page) { var result = CreateOneNoteItemResult(page, false); result.SubTitle = $"{page.LastModified.Humanize()} | {result.SubTitle}"; @@ -234,92 +233,59 @@ public Result CreateRecentPageResult(OneNotePage page) return result; } - - private bool CreateNewItem(T parent, string name, bool validTitle, ActionContext c, Func createAction) where T : IOneNoteItem + //When name can have invalid chars + private Result CreateNewItemResult(string newName, TParent? parent, string iconPath, Func createFunc) + where TNew : IOneNoteItem, INameInvalidCharacters + where TParent : IOneNoteItem + => CreateNewItemResult(newName, parent, iconPath, createFunc, OneNoteApp.IsValidName(newName), TNew.InvalidCharacters); + + private Result CreateNewItemResult(string newName, TParent? parent, string iconPath, Func createFunc, bool validTitle = true, IReadOnlyList? invalidChars = null) + where TNew : IOneNoteItem + where TParent : IOneNoteItem { - if (!validTitle) + newName = newName.Trim(); + string type = nameof(TNew); + return new Result { - return false; - } + Title = $"Create {type.Transform(To.LowerCase)} \"{newName}\"", + SubTitle = validTitle + ? parent == null // parent is null if trying to create a notebook + ? $"Location: {OneNoteApp.GetDefaultNotebookLocation()}" + : $"Path: {GetNicePath(parent)}{PathSeparator}{newName}" + : $"{type.Transform(To.SentenceCase)} names cannot contain: {string.Join(' ', invalidChars!)}", + AutoCompleteText = parent == null ? $"{ActionKeyword} {settings.Keywords.NotebookExplorer}{newName}" : $"{GetAutoCompleteText(parent)}{newName}", + IcoPath = iconPath, + Action = c => + { + if (!validTitle) + return false; - bool showOneNote = !c.SpecialKeyState.CtrlPressed; + bool showOneNote = !c.SpecialKeyState.CtrlPressed; + createFunc(parent, newName, showOneNote ? OpenMode.ExistingOrNewWindow : OpenMode.None); - createAction(parent, name, showOneNote); - - context.API.ReQuery(); - - if(showOneNote) - WindowHelper.FocusOneNote(); - - return showOneNote; - } + context.API.ReQuery(); - public Result CreateNewPageResult(string newPageName, OneNoteSection section) - { - newPageName = newPageName.Trim(); - return new Result - { - Title = $"Create page: \"{newPageName}\"", - SubTitle = $"Path: {GetNicePath(section)}{PathSeparator}{newPageName}", - AutoCompleteText = $"{GetAutoCompleteText(section)}{newPageName}", - IcoPath = iconProvider.NewPage, - PreviewPanel = GetNewPagePreviewPanel(section, newPageName), - Action = c => CreateNewItem(section, newPageName, true, c, OneNoteApplication.CreatePage), + if (showOneNote) + WindowHelper.FocusOneNote(); + + return showOneNote; + }, }; } - public Result CreateNewSectionResult(string newSectionName, INotebookOrSectionGroup parent) + public Result CreateNewPageResult(string newPageName, Section section) { - newSectionName = newSectionName.Trim(); - bool validTitle = OneNoteApplication.IsSectionNameValid(newSectionName); - - return new Result - { - Title = $"Create section: \"{newSectionName}\"", - SubTitle = validTitle - ? $"Path: {GetNicePath(parent)}{PathSeparator}{newSectionName}" - : $"Section names cannot contain: {string.Join(' ', OneNoteApplication.InvalidSectionChars)}", - AutoCompleteText = $"{GetAutoCompleteText(parent)}{newSectionName}", - IcoPath = iconProvider.NewSection, - Action = c => CreateNewItem(parent, newSectionName, validTitle, c, OneNoteApplication.CreateSection), - }; + var result = CreateNewItemResult(newPageName, section, iconProvider.NewPage, OneNoteApp.CreatePage); + result.PreviewPanel = GetNewPagePreviewPanel(section, newPageName); + return result; } - public Result CreateNewSectionGroupResult(string newSectionGroupName, INotebookOrSectionGroup parent) - { - newSectionGroupName = newSectionGroupName.Trim(); - bool validTitle = OneNoteApplication.IsSectionGroupNameValid(newSectionGroupName); + public Result CreateNewSectionResult(string newSectionName, INotebookOrSectionGroup parent) => CreateNewItemResult(newSectionName, parent, iconProvider.NewSection, OneNoteApp.CreateSection); - return new Result - { - Title = $"Create section group: \"{newSectionGroupName}\"", - SubTitle = validTitle - ? $"Path: {GetNicePath(parent)}{PathSeparator}{newSectionGroupName}" - : $"Section group names cannot contain: {string.Join(' ', OneNoteApplication.InvalidSectionGroupChars)}", - AutoCompleteText = $"{GetAutoCompleteText(parent)}{newSectionGroupName}", - IcoPath = iconProvider.NewSectionGroup, - Action = c => CreateNewItem(parent, newSectionGroupName, validTitle, c, OneNoteApplication.CreateSectionGroup), - }; - } - - public Result CreateNewNotebookResult(string newNotebookName) - { - newNotebookName = newNotebookName.Trim(); - bool validTitle = OneNoteApplication.IsNotebookNameValid(newNotebookName); + public Result CreateNewSectionGroupResult(string newSectionGroupName, INotebookOrSectionGroup parent) => CreateNewItemResult(newSectionGroupName, parent, iconProvider.NewSectionGroup, OneNoteApp.CreateSectionGroup); + + public Result CreateNewNotebookResult(string newNotebookName) => CreateNewItemResult(newNotebookName, null, iconProvider.NewNotebook, (_, name, openMode) => OneNoteApp.CreateNotebook(name, openMode)); - return new Result - { - Title = $"Create notebook: \"{newNotebookName}\"", - SubTitle = validTitle - ? $"Location: {OneNoteApplication.GetDefaultNotebookLocation()}" - : $"Notebook names cannot contain: {string.Join(' ', OneNoteApplication.InvalidNotebookChars)}", - AutoCompleteText = $"{ActionKeyword} {settings.Keywords.NotebookExplorer}{newNotebookName}", - IcoPath = iconProvider.NewNotebook, - Action = c => CreateNewItem(null, newNotebookName, validTitle, - c, (_, name, valid) => OneNoteApplication.CreateNotebook(name, valid)), - }; - } - public List ContextMenu(Result selectedResult) { var results = new List(); @@ -341,7 +307,7 @@ public List ContextMenu(Result selectedResult) AddSelectedCount = false, Action = _ => { - OneNoteApplication.ComObject.NavigateTo(item.ID, fNewWindow: true); + OneNoteApp.Open(item, true); WindowHelper.FocusOneNote(); return true; } @@ -376,7 +342,7 @@ public List EmptyCollection(List results, IOneNoteItem? parent) results.Add(EmptyCollectionResult("section", iconProvider.NewSection, "(unencrypted) section")); results.Add(EmptyCollectionResult("section group", iconProvider.NewSectionGroup)); break; - case OneNoteSection section: + case Section section: // Can create page if (!section.Locked) { @@ -387,7 +353,7 @@ public List EmptyCollection(List results, IOneNoteItem? parent) return results; - Result EmptyCollectionResult(string title, string iconPath, string? subTitle = null, OneNoteSection? section = null) + Result EmptyCollectionResult(string title, string iconPath, string? subTitle = null, Section? section = null) { return new Result { @@ -399,7 +365,7 @@ Result EmptyCollectionResult(string title, string iconPath, string? subTitle = n } } - private Lazy GetNewPagePreviewPanel(OneNoteSection? section, string? pageTitle) + private Lazy GetNewPagePreviewPanel(Section? section, string? pageTitle) => new(() => new NewOneNotePagePreviewPanel(context, section, pageTitle)); public static List NoMatchesFound() @@ -437,6 +403,5 @@ private static List SingleResult(string title, string subTitle, string i } }; } - } } \ No newline at end of file From 59f3f2becda191e09622dbd4e55ba403ca634db3 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Thu, 8 Jan 2026 18:55:10 +0000 Subject: [PATCH 14/29] Style tweaks --- Flow.Launcher.Plugin.OneNote/Main.cs | 2 +- .../Search/NotebookExplorer.cs | 4 ++-- Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs | 4 +++- .../Search/SearchManager.cs | 1 - Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs | 2 +- .../UI/ViewModels/NewOneNotePageViewModel.cs | 14 +++++++++++++- 6 files changed, 20 insertions(+), 7 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/Main.cs b/Flow.Launcher.Plugin.OneNote/Main.cs index 95752a0..dd3501f 100644 --- a/Flow.Launcher.Plugin.OneNote/Main.cs +++ b/Flow.Launcher.Plugin.OneNote/Main.cs @@ -59,8 +59,8 @@ private static async Task OneNoteInitAsync(CancellationToken token) { semaphore.Release(); } - } + public async Task> QueryAsync(Query query, CancellationToken token) { Task init = OneNoteInitAsync(token); diff --git a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs index e2a175d..783311d 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs @@ -74,7 +74,7 @@ private List ShowAll(IOneNoteItem? parent, IEnumerable col .Select(item => resultCreator.CreateOneNoteItemResult(item, true)) .ToList(); - return results.Any() ? results : resultCreator.EmptyCollection(results, parent); + return results.Count != 0 ? results : resultCreator.EmptyCollection(results, parent); } private List ScopedSearch(string query, IOneNoteItem parent) @@ -91,7 +91,7 @@ private List ScopedSearch(string query, IOneNoteItem parent) .Select(pg => resultCreator.CreatePageResult(pg, currentSearch)) .ToList(); - return results.Any() ? results : ResultCreator.NoMatchesFound(); + return results.Count != 0 ? results : ResultCreator.NoMatchesFound(); } private List Explorer(string search, IOneNoteItem? parent, IEnumerable collection) diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs index 794681e..cc54ff1 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs @@ -7,8 +7,10 @@ public abstract class SearchBase protected readonly PluginInitContext context; protected readonly Settings settings; protected readonly ResultCreator resultCreator; +#nullable disable public readonly Keyword keyword; - protected SearchBase(PluginInitContext context, Settings settings, ResultCreator resultCreator, Keyword keyword) +#nullable restore + protected SearchBase(PluginInitContext context, Settings settings, ResultCreator resultCreator, Keyword? keyword) { this.context = context; this.settings = settings; diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs index 8c8c7cf..35a5ae7 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs @@ -15,7 +15,6 @@ public SearchManager(PluginInitContext context, Settings settings, ResultCreator notebookExplorer = new NotebookExplorer(context, settings, resultCreator, titleSearch); recentPages = new RecentPages(context, settings, resultCreator); defaultSearch = new DefaultSearch(context, settings, resultCreator); - } public List Query(string search) diff --git a/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs index 2431392..4c7101d 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs @@ -25,7 +25,7 @@ public List Filter(string query, IOneNoteItem? parent, IEnumerable resultCreator.CreateOneNoteItemResult(x.Item, false, x.HighlightData, x.Score)) .ToList(); - return results.Any() ? results : ResultCreator.NoMatchesFound(); + return results.Count != 0 ? results : ResultCreator.NoMatchesFound(); } } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs index cde0577..bd6e229 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs @@ -25,8 +25,20 @@ public NewOneNotePageViewModel(PluginInitContext context, Section? section, stri private void CreatePage(bool openImmediately) { var page = OneNoteApp.CreatePage(section, PageTitle); + var xmlWrap = $""" + + + + + + + + + + + + """; var pageContentXml = page.GetPageContent(); - var xmlWrap = $""; pageContentXml = pageContentXml.Insert(pageContentXml.IndexOf("", StringComparison.Ordinal), xmlWrap); OneNoteApp.UpdatePageContent(pageContentXml); context.API.ReQuery(); From d34a9a2cddd73011989ea45b9eb5cd1bc0c00b88 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Thu, 8 Jan 2026 21:08:02 +0000 Subject: [PATCH 15/29] Improve notebook explorer performance Cache relative paths of items, as `IOneNoteItem.GetRelativePath` builds a string everytime. Cache the OneNote hierarchy, only query once while traversing. --- Changelog.md | 1 + Flow.Launcher.Plugin.OneNote/Main.cs | 27 ++++++++-------- Flow.Launcher.Plugin.OneNote/ResultCreator.cs | 6 +++- .../Search/NotebookExplorer.cs | 29 +++++++++++++---- .../Search/SearchManager.cs | 11 ++++--- .../VisibilityChanged.cs | 32 +++++++++++++++++++ 6 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 Flow.Launcher.Plugin.OneNote/VisibilityChanged.cs diff --git a/Changelog.md b/Changelog.md index 9228a5f..52e6811 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,7 @@ - Update to Net 9.0 - Refactored code - Migrate from `Odotocodot.OneNote.Linq 1.2.0` to `LinqToOneNote 2.0.0` +- Improve notebook explorer performance. ## 2.1.2 - 2025-6-11 diff --git a/Flow.Launcher.Plugin.OneNote/Main.cs b/Flow.Launcher.Plugin.OneNote/Main.cs index dd3501f..a2184b8 100644 --- a/Flow.Launcher.Plugin.OneNote/Main.cs +++ b/Flow.Launcher.Plugin.OneNote/Main.cs @@ -9,7 +9,7 @@ using OneNoteApp = LinqToOneNote.OneNote; namespace Flow.Launcher.Plugin.OneNote { - #nullable disable +#nullable disable public class Main : IAsyncPlugin, IContextMenu, ISettingProvider, IDisposable { private PluginInitContext context; @@ -18,6 +18,7 @@ public class Main : IAsyncPlugin, IContextMenu, ISettingProvider, IDisposable private SearchManager searchManager; private Settings settings; private IconProvider iconProvider; + private VisibilityChanged visibilityChanged; private static SemaphoreSlim semaphore; @@ -26,21 +27,19 @@ public Task InitAsync(PluginInitContext context) { this.context = context; settings = context.API.LoadSettingJsonStorage(); - + + visibilityChanged = new VisibilityChanged(context); iconProvider = new IconProvider(context, settings); resultCreator = new ResultCreator(context, settings, iconProvider); - searchManager = new SearchManager(context, settings, resultCreator); - semaphore = new SemaphoreSlim(1,1); - context.API.VisibilityChanged += OnVisibilityChanged; - return Task.CompletedTask; - } + searchManager = new SearchManager(context, settings, resultCreator, visibilityChanged); + semaphore = new SemaphoreSlim(1, 1); - private void OnVisibilityChanged(object _, VisibilityChangedEventArgs e) - { - if (context.CurrentPluginMetadata.Disabled || !e.IsVisible) + visibilityChanged.Subscribe(static (isVisible) => { - Task.Run(OneNoteApp.ReleaseComObject); - } + if (!isVisible) + Task.Run(OneNoteApp.ReleaseComObject); + }); + return Task.CompletedTask; } private static async Task OneNoteInitAsync(CancellationToken token) @@ -70,7 +69,7 @@ public async Task> QueryAsync(Query query, CancellationToken token) await init; - return searchManager.Query(query.Search); + return searchManager.Query(query); } public List LoadContextMenus(Result selectedResult) @@ -85,7 +84,7 @@ public Control CreateSettingPanel() public void Dispose() { - context.API.VisibilityChanged -= OnVisibilityChanged; + visibilityChanged.Dispose(); semaphore.Dispose(); OneNoteApp.ReleaseComObject(); } diff --git a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs index 98ea5eb..26be981 100644 --- a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs +++ b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -16,6 +17,7 @@ public class ResultCreator private readonly PluginInitContext context; private readonly Settings settings; private readonly IconProvider iconProvider; + private readonly ConcurrentDictionary relativePaths = []; private const string PathSeparator = " > "; private const string BulletPoint = "\u2022 "; @@ -28,7 +30,9 @@ public ResultCreator(PluginInitContext context, Settings settings, IconProvider this.context = context; } - private static string GetNicePath(IOneNoteItem item, string separator = PathSeparator) => item.GetRelativePath(false, separator); + private readonly record struct RelativePathKey(IOneNoteItem Item, string Separator); + private string GetNicePath(IOneNoteItem item, string separator = PathSeparator) + => relativePaths.GetOrAdd(new RelativePathKey(item, separator), v => v.Item.GetRelativePath(false, v.Separator)); private string GetTitle(IOneNoteItem item, List? highlightData) { diff --git a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs index 783311d..cdb1a26 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs @@ -11,13 +11,22 @@ namespace Flow.Launcher.Plugin.OneNote.Search public class NotebookExplorer : SearchBase { private readonly TitleSearch titleSearch; - public NotebookExplorer(PluginInitContext context, Settings settings, ResultCreator resultCreator, TitleSearch titleSearch) + private Root? cache; + private bool updateCache; + public NotebookExplorer(PluginInitContext context, Settings settings, ResultCreator resultCreator, TitleSearch titleSearch, VisibilityChanged visibilityChanged) : base(context, settings, resultCreator, settings.Keywords.NotebookExplorer) { this.titleSearch = titleSearch; + visibilityChanged.Subscribe(isVisible => + { + if (!isVisible) + { + updateCache = true; + } + }); } - public override List GetResults(string query) + internal List GetResults(Query query) { if (!ValidateSearch(query, out string? search, out IOneNoteItem? parent, out IEnumerable collection)) return resultCreator.InvalidQuery(false); @@ -40,13 +49,21 @@ public override List GetResults(string query) return results; } - private bool ValidateSearch(string query, out string? lastSearch, out IOneNoteItem? parent, out IEnumerable collection) + public override List GetResults(string query) => GetResults(query); + + private bool ValidateSearch(Query query, out string? lastSearch, out IOneNoteItem? parent, out IEnumerable collection) { lastSearch = null; parent = null; - collection = OneNoteApp.GetFullHierarchy().Notebooks; - - string search = query[(query.IndexOf(Keywords.NotebookExplorer, StringComparison.Ordinal) + Keywords.NotebookExplorer.Length)..]; + if (updateCache || query.IsReQuery || cache == null) + { + cache = OneNoteApp.GetFullHierarchy(); + updateCache = false; + } + + collection = cache.Notebooks; + + string search = query.Search[(query.Search.IndexOf(Keywords.NotebookExplorer, StringComparison.Ordinal) + Keywords.NotebookExplorer.Length)..]; const string separator = Keywords.NotebookExplorerSeparator; var currIndex = search.IndexOf(separator, StringComparison.Ordinal); var prevIndex = 0; diff --git a/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs index 35a5ae7..6581584 100644 --- a/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs @@ -9,20 +9,21 @@ public class SearchManager private readonly DefaultSearch defaultSearch; private readonly RecentPages recentPages; - public SearchManager(PluginInitContext context, Settings settings, ResultCreator resultCreator) + public SearchManager(PluginInitContext context, Settings settings, ResultCreator resultCreator, VisibilityChanged visibilityChanged) { titleSearch = new TitleSearch(context, settings, resultCreator); - notebookExplorer = new NotebookExplorer(context, settings, resultCreator, titleSearch); + notebookExplorer = new NotebookExplorer(context, settings, resultCreator, titleSearch, visibilityChanged); recentPages = new RecentPages(context, settings, resultCreator); defaultSearch = new DefaultSearch(context, settings, resultCreator); } - - public List Query(string search) + + public List Query(Query query) { + string search = query.Search; return search switch { { } when search.StartsWithOrd(titleSearch.keyword) => titleSearch.GetResults(search), - { } when search.StartsWithOrd(notebookExplorer.keyword) => notebookExplorer.GetResults(search), + { } when search.StartsWithOrd(notebookExplorer.keyword) => notebookExplorer.GetResults(query), { } when search.StartsWithOrd(recentPages.keyword) => recentPages.GetResults(search), _ => defaultSearch.GetResults(search!), }; diff --git a/Flow.Launcher.Plugin.OneNote/VisibilityChanged.cs b/Flow.Launcher.Plugin.OneNote/VisibilityChanged.cs new file mode 100644 index 0000000..85c026d --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/VisibilityChanged.cs @@ -0,0 +1,32 @@ +namespace Flow.Launcher.Plugin.OneNote +{ + public delegate void VisibilityChangedEventHandler(bool isVisible); + public class VisibilityChanged + { + private event VisibilityChangedEventHandler? OnVisibilityChanged; + private readonly PluginInitContext context; + public VisibilityChanged(PluginInitContext context) + { + this.context = context; + context.API.VisibilityChanged += OnVisibilityChangedWrap; + } + + private void OnVisibilityChangedWrap(object _, VisibilityChangedEventArgs e) + { + if (!context.CurrentPluginMetadata.Disabled) + { + OnVisibilityChanged?.Invoke(e.IsVisible); + } + } + public void Subscribe(VisibilityChangedEventHandler action) + { + OnVisibilityChanged += action; + } + + public void Dispose() + { + context.API.VisibilityChanged -= OnVisibilityChangedWrap; + OnVisibilityChanged = null; + } + } +} \ No newline at end of file From d96f4e1b19ade0b098bbd0977963c7c7fbc69752 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sat, 14 Feb 2026 23:06:49 +0000 Subject: [PATCH 16/29] Remove uneeded path caching --- Flow.Launcher.Plugin.OneNote/ResultCreator.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs index 26be981..07caabe 100644 --- a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs +++ b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs @@ -17,7 +17,6 @@ public class ResultCreator private readonly PluginInitContext context; private readonly Settings settings; private readonly IconProvider iconProvider; - private readonly ConcurrentDictionary relativePaths = []; private const string PathSeparator = " > "; private const string BulletPoint = "\u2022 "; @@ -30,9 +29,7 @@ public ResultCreator(PluginInitContext context, Settings settings, IconProvider this.context = context; } - private readonly record struct RelativePathKey(IOneNoteItem Item, string Separator); - private string GetNicePath(IOneNoteItem item, string separator = PathSeparator) - => relativePaths.GetOrAdd(new RelativePathKey(item, separator), v => v.Item.GetRelativePath(false, v.Separator)); + private static string GetNicePath(IOneNoteItem item, string separator = PathSeparator) => item.GetRelativePath(false, separator); private string GetTitle(IOneNoteItem item, List? highlightData) { From dab09f4c96ca99b30720bd06c84eec070e3dafda Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:30:51 +0000 Subject: [PATCH 17/29] Small refactor Update Flow.Launcher.Plugin to 5.2.0 --- .gitignore | 1 + Changelog.md | 9 +++++---- .../Flow.Launcher.Plugin.OneNote.csproj | 2 +- Flow.Launcher.Plugin.OneNote/ResultCreator.cs | 10 +++++----- Flow.Launcher.Plugin.OneNote/plugin.json | 2 +- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 14de929..0085abe 100644 --- a/.gitignore +++ b/.gitignore @@ -353,3 +353,4 @@ MigrationBackup/ #VSCode .vscode/ +.idea/ diff --git a/Changelog.md b/Changelog.md index 52e6811..c1f206e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,11 +2,12 @@ ## 3.0.0 - 2026-#-## - **⚠ Now requires Flow Launcher version 2.0.0 or later.** -- Update Flow.Launcher.Plugin to 5.0.0 -- Update to Net 9.0 - Refactored code -- Migrate from `Odotocodot.OneNote.Linq 1.2.0` to `LinqToOneNote 2.0.0` -- Improve notebook explorer performance. +- Improved notebook explorer performance. +- Dependencies + - Updated to Net 9.0 + - Updated `Flow.Launcher.Plugin-4.1.0` to `Flow.Launcher.Plugin-5.2.0` + - Updated `Odotocodot.OneNote.Linq-1.2.0` to `LinqToOneNote-2.0.0` ## 2.1.2 - 2025-6-11 diff --git a/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj b/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj index a048bd3..6018f5f 100644 --- a/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj +++ b/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj @@ -28,7 +28,7 @@ - + diff --git a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs index 07caabe..5a3fdf1 100644 --- a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs +++ b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs @@ -302,8 +302,8 @@ public List ContextMenu(Result selectedResult) results.Add(new Result { - Title = "Open in new OneNote window", - IcoPath = IconProvider.Logo, + Title = $"Open \"{item.Name}\" in new OneNote window", + Icon = result.Icon, Score = 20, AddSelectedCount = false, Action = _ => @@ -320,7 +320,7 @@ public List ContextMenu(Result selectedResult) Title = "Show in Notebook Explorer", SubTitle = autoCompleteText, AddSelectedCount = false, - Score = 10, + Score = 0, IcoPath = iconProvider.NotebookExplorer, Action = _ => { @@ -383,13 +383,13 @@ public List InvalidQuery(bool includeSubtitle = true) : string.Empty, iconProvider.Warning); } - public List SearchType(string title, string? parentName) + public List SearchType(string title, string? parentName, string subTitle = "") { if (!string.IsNullOrWhiteSpace(parentName)) { title += $" in \"{parentName}\""; } - return SingleResult(title, string.Empty, iconProvider.Search); + return SingleResult(title, subTitle, iconProvider.Search); } private static List SingleResult(string title, string subTitle, string iconPath) diff --git a/Flow.Launcher.Plugin.OneNote/plugin.json b/Flow.Launcher.Plugin.OneNote/plugin.json index d1bc17e..f88022f 100644 --- a/Flow.Launcher.Plugin.OneNote/plugin.json +++ b/Flow.Launcher.Plugin.OneNote/plugin.json @@ -4,7 +4,7 @@ "Name": "OneNote", "Description": "Search and create your OneNote notes", "Author": "Odotocodot", - "Version": "2.2.0-beta", + "Version": "3.0.0-beta", "Language": "csharp", "Website": "https://github.com/Odotocodot/Flow.Launcher.Plugin.OneNote", "IcoPath": "Images/logo.png", From 7b5be2747e6af3d1aa7b982b3020a77bd47005a3 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Tue, 3 Mar 2026 23:57:11 +0000 Subject: [PATCH 18/29] Add copy link to clipboard command #32 --- .../Icons/IconProvider.cs | 1 + Flow.Launcher.Plugin.OneNote/ResultCreator.cs | 18 ++++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs b/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs index d29316a..dae90ea 100644 --- a/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs +++ b/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs @@ -30,6 +30,7 @@ public class IconProvider : BaseModel public string Warning => settings.IconTheme == IconTheme.Color ? $"{IC.ImagesDirectory}{IC.Warning}.{GetIconThemeString(IconTheme.Dark)}.png" : GetIconPath(IC.Warning); + public static GlyphInfo Clipboard { get; } = new("/Resources/#Segoe Fluent Icons", "\uf0e3"); // Clipboard public int CachedIconCount => iconCache.Keys.Count(k => char.IsDigit(k.Split('.')[1][1])); public DirectoryInfo GeneratedImagesDirectoryInfo { get; } diff --git a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs index 5a3fdf1..6b515a9 100644 --- a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs +++ b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -313,6 +312,7 @@ public List ContextMenu(Result selectedResult) return true; } }); + string autoCompleteText = GetAutoCompleteText(item); results.Add(new Result @@ -320,7 +320,7 @@ public List ContextMenu(Result selectedResult) Title = "Show in Notebook Explorer", SubTitle = autoCompleteText, AddSelectedCount = false, - Score = 0, + Score = 10, IcoPath = iconProvider.NotebookExplorer, Action = _ => { @@ -329,6 +329,20 @@ public List ContextMenu(Result selectedResult) return false; } }); + + results.Add(new Result + { + Title = "Copy link to clipboard", + Glyph = IconProvider.Clipboard, + Score = 0, + AddSelectedCount = false, + Action = _ => + { + OneNoteApp.ComObject.GetHyperlinkToObject(item.Id, string.Empty, out var hyperlink); + context.API.CopyToClipboard(hyperlink); + return true; + } + }); } return results; } From 9cbc7b8eb78c9fde1878e190d2b740ded85efba0 Mon Sep 17 00:00:00 2001 From: Odotocodot <48138990+Odotocodot@users.noreply.github.com> Date: Sun, 11 Jan 2026 13:51:58 +0000 Subject: [PATCH 19/29] Add design time UI for settings page Makes viewing/editing the not suck as much --- .../UI/DesignTimeStyles.xaml | 292 ++++++++++++++++++ Flow.Launcher.Plugin.OneNote/UI/Styles.xaml | 6 + 2 files changed, 298 insertions(+) create mode 100644 Flow.Launcher.Plugin.OneNote/UI/DesignTimeStyles.xaml diff --git a/Flow.Launcher.Plugin.OneNote/UI/DesignTimeStyles.xaml b/Flow.Launcher.Plugin.OneNote/UI/DesignTimeStyles.xaml new file mode 100644 index 0000000..1ab8c81 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/UI/DesignTimeStyles.xaml @@ -0,0 +1,292 @@ + + + + + Off + On + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/Styles.xaml b/Flow.Launcher.Plugin.OneNote/UI/Styles.xaml index c5dc3ab..7f27e25 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/Styles.xaml +++ b/Flow.Launcher.Plugin.OneNote/UI/Styles.xaml @@ -29,6 +29,12 @@ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --> + + + + + +