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