diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d1e853d..4101f65 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,7 +23,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v3 with: - dotnet-version: 7.0.x + dotnet-version: 9.x - name: Get version id: version 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 32ddd3c..4e0705e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,10 +1,22 @@ # Changelog -## 2.1.2 - 2025-6-11 +## 3.0.0 - 2026-03-04 + +- **⚠ Now requires Flow Launcher version 2.1.0 or later.** +- Refactored code +- Improved Notebook Explorer performance. +- Added copy link to clipboard context menu item. ([#32](https://github.com/Odotocodot/Flow.Launcher.Plugin.OneNote/issues/32)) +- 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` + - Migrated from `ModernWpfUI` to `iNKORE.UI.WPF.Modern` + +## 2.1.2 - 2025-06-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)) -## 2.1.1 - 2025-1-4 +## 2.1.1 - 2025-01-04 ### Changes @@ -17,7 +29,7 @@ - Fixed spelling and grammar mistakes. -## 2.1.0 - 2024-6-24 +## 2.1.0 - 2024-06-24 ### Added diff --git a/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj b/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj index ffdf7d0..4c07142 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 @@ -10,6 +10,7 @@ flow-launcher flow-plugin false true + enable true true en @@ -27,11 +28,11 @@ - + - - + + diff --git a/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs b/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs index dfe0df2..fdb9d41 100644 --- a/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs +++ b/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs @@ -1,30 +1,32 @@ using System.Drawing; -using Odotocodot.OneNote.Linq; +using LinqToOneNote; 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(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 Notebook n: + prefix = IconConstants.Notebook; + color = n.Color; + break; + case SectionGroup sg: + prefix = sg.IsRecycleBin ? IconConstants.RecycleBin : IconConstants.SectionGroup; + break; + case Section s: + prefix = IconConstants.Section; + color = s.Color; + break; + case Page: + prefix = IconConstants.Page; + break; + } } } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs b/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs index 2e12698..dae90ea 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,13 @@ 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 static GlyphInfo Clipboard { get; } = new("/Resources/#Segoe Fluent Icons", "\uf0e3"); // Clipboard - 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 +61,7 @@ private static string GetIconThemeString(IconTheme iconTheme) { iconTheme = FlowLauncherThemeToIconTheme(); } - return Enum.GetName(iconTheme).ToLower(); + return iconTheme.ToString().ToLower(); } private static IconTheme FlowLauncherThemeToIconTheme() @@ -79,38 +80,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 0273790..bc06637 100644 --- a/Flow.Launcher.Plugin.OneNote/Keywords.cs +++ b/Flow.Launcher.Plugin.OneNote/Keywords.cs @@ -1,11 +1,41 @@ -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 + { + public Keyword(string value) => Value = value; + public string Value { get; private set; } + + public void ChangeKeyword(string newValue) => Value = newValue; + + 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/Main.cs b/Flow.Launcher.Plugin.OneNote/Main.cs index 4bdb0b8..a2184b8 100644 --- a/Flow.Launcher.Plugin.OneNote/Main.cs +++ b/Flow.Launcher.Plugin.OneNote/Main.cs @@ -4,10 +4,12 @@ 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; +using OneNoteApp = LinqToOneNote.OneNote; namespace Flow.Launcher.Plugin.OneNote { +#nullable disable public class Main : IAsyncPlugin, IContextMenu, ISettingProvider, IDisposable { private PluginInitContext context; @@ -16,45 +18,51 @@ public class Main : IAsyncPlugin, IContextMenu, ISettingProvider, IDisposable private SearchManager searchManager; private Settings settings; private IconProvider iconProvider; + private VisibilityChanged visibilityChanged; private static SemaphoreSlim semaphore; - private static Main instance; + - private Query currentQuery; 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; - instance = this; + searchManager = new SearchManager(context, settings, resultCreator, visibilityChanged); + semaphore = new SemaphoreSlim(1, 1); + + visibilityChanged.Subscribe(static (isVisible) => + { + if (!isVisible) + Task.Run(OneNoteApp.ReleaseComObject); + }); return Task.CompletedTask; } - public void OnVisibilityChanged(object _, VisibilityChangedEventArgs e) + private static async Task OneNoteInitAsync(CancellationToken token) { - if (context.CurrentPluginMetadata.Disabled || !e.IsVisible) + if (OneNoteApp.HasComObject) + return; + + if (!await semaphore.WaitAsync(0,token)) + return; + + try { - OneNoteApplication.ReleaseComObject(); + OneNoteApp.InitComObject(); + } + finally + { + semaphore.Release(); } } - private static async Task OneNoteInitAsync(CancellationToken token = default) - { - if (semaphore.CurrentCount == 0 || OneNoteApplication.HasComObject) - return; - - await semaphore.WaitAsync(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,8 +72,6 @@ public async Task> QueryAsync(Query query, CancellationToken token) return searchManager.Query(query); } - public static void ForceReQuery() => instance.context.API.ChangeQuery(instance.currentQuery.RawQuery, true); - public List LoadContextMenus(Result selectedResult) { return resultCreator.ContextMenu(selectedResult); @@ -78,9 +84,9 @@ public Control CreateSettingPanel() public void Dispose() { - context.API.VisibilityChanged -= OnVisibilityChanged; + visibilityChanged.Dispose(); semaphore.Dispose(); - OneNoteApplication.ReleaseComObject(); + OneNoteApp.ReleaseComObject(); } } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs index 4824b43..bb954ea 100644 --- a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs +++ b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs @@ -2,11 +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 LinqToOneNote; +using LinqToOneNote.Abstractions; +using OneNoteApp = LinqToOneNote.OneNote; 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) { @@ -28,10 +28,9 @@ 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) + private string GetTitle(IOneNoteItem item, List? highlightData) { string title = item.Name; if (!item.IsUnread || !settings.ShowUnread) @@ -49,9 +48,11 @@ 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 Page ? string.Empty : Keywords.NotebookExplorerSeparator; + return $"{ActionKeyword} {settings.Keywords.NotebookExplorer}{GetNicePath(item, Keywords.NotebookExplorerSeparator)}{slash}"; + } public List EmptyQuery() { @@ -63,7 +64,8 @@ public List EmptyQuery() SubTitle = "Try typing something!", AutoCompleteText = ActionKeyword, IcoPath = iconProvider.Search, - Score = 5000, + AddSelectedCount = false, + Score = Result.MaxScore, }, new Result { @@ -71,6 +73,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 +87,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,11 +99,12 @@ public List EmptyQuery() { Title = "New quick note", IcoPath = iconProvider.QuickNote, + AddSelectedCount = false, Score = -4000, PreviewPanel = GetNewPagePreviewPanel(null, null), Action = _ => { - OneNoteApplication.CreateQuickNote(true); + OneNoteApp.CreateQuickNote(OpenMode.ExistingOrNewWindow); WindowHelper.FocusOneNote(); return true; }, @@ -108,20 +113,21 @@ public List EmptyQuery() { Title = "Open and sync notebooks", IcoPath = iconProvider.Sync, + AddSelectedCount = false, 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; @@ -130,47 +136,34 @@ 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} - - Contains: - {TrianglePoint}{"section group".ToQuantity(notebook.SectionGroups.Count())} - {TrianglePoint}{"section".ToQuantity(notebook.Sections.Count())} - {TrianglePoint}{"page".ToQuantity(notebook.GetPages().Count())} - """; + {TrianglePoint}{i.LastModified:F} - 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())} + {TrianglePoint}{"section group".ToQuantity(i.SectionGroups.Count)} + {TrianglePoint}{"section".ToQuantity(i.Sections.Count)} + {TrianglePoint}{"page".ToQuantity(i.GetAllPages().Count())} """; - iconInfo = new IconGeneratorInfo(sectionGroup); + if (i is Notebook) + { + subTitle = string.Empty; + } break; - case OneNoteSection section: + case Section section: if (section.Encrypted) { title += $" [Encrypted] {(section.Locked ? "[Locked]" : "[Unlocked]")}"; @@ -182,12 +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())} """; - - iconInfo = new IconGeneratorInfo(section); break; - case OneNotePage page: + case Page page: autoCompleteText = actionIsAutoComplete ? autoCompleteText[..^1] : string.Empty; actionIsAutoComplete = false; @@ -198,10 +189,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,11 +209,11 @@ public Result CreateOneNoteItemResult(IOneNoteItem item, bool actionIsAutoComple context.API.ChangeQuery($"{autoCompleteText}", true); return false; } - + await Task.Run(() => { item.Sync(); - item.OpenInOneNote(); + item.Open(); }); WindowHelper.FocusOneNote(); return true; @@ -234,157 +221,71 @@ 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, null); + var result = CreateOneNoteItemResult(page, false); result.SubTitle = $"{page.LastModified.Humanize()} | {result.SubTitle}"; result.IcoPath = iconProvider.Recent; + result.AddSelectedCount = false; return result; } - 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 => - { - bool showOneNote = !c.SpecialKeyState.CtrlPressed; - - OneNoteApplication.CreatePage(section, newPageName, showOneNote); - Main.ForceReQuery(); - - if(showOneNote) - WindowHelper.FocusOneNote(); - - return showOneNote; - }, - }; - } + //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); - public Result CreateNewSectionResult(string newSectionName, IOneNoteItem parent) + private Result CreateNewItemResult(string newName, TParent? parent, string iconPath, Func createFunc, bool validTitle = true, IReadOnlyList? invalidChars = null) + where TNew : IOneNoteItem + where TParent : IOneNoteItem { - newSectionName = newSectionName.Trim(); - bool validTitle = OneNoteApplication.IsSectionNameValid(newSectionName); - + newName = newName.Trim(); + string type = typeof(TNew).Name; return new Result { - Title = $"Create section: \"{newSectionName}\"", + Title = $"Create new {type.Humanize().ToLower()}: \"{newName}\"", SubTitle = validTitle - ? $"Path: {GetNicePath(parent)}{PathSeparator}{newSectionName}" - : $"Section names cannot contain: {string.Join(' ', OneNoteApplication.InvalidSectionChars)}", - AutoCompleteText = $"{GetAutoCompleteText(parent)}{newSectionName}", - IcoPath = iconProvider.NewSection, + ? parent == null // parent is null if trying to create a notebook + ? $"Location: {OneNoteApp.GetDefaultNotebookLocation()}" + : $"Path: {GetNicePath(parent)}{PathSeparator}{newName}" + : $"{type.Humanize()} 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; - - 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; - }, - }; - } + createFunc(parent, newName, showOneNote ? OpenMode.ExistingOrNewWindow : OpenMode.None); - public Result CreateNewSectionGroupResult(string newSectionGroupName, IOneNoteItem parent) - { - newSectionGroupName = newSectionGroupName.Trim(); - bool validTitle = OneNoteApplication.IsSectionGroupNameValid(newSectionGroupName); + context.API.ReQuery(); - 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 => - { - 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) + if (showOneNote) WindowHelper.FocusOneNote(); - + return showOneNote; }, }; } - public Result CreateNewNotebookResult(string newNotebookName) + public Result CreateNewPageResult(string newPageName, Section section) { - newNotebookName = newNotebookName.Trim(); - bool validTitle = OneNoteApplication.IsNotebookNameValid(newNotebookName); + var result = CreateNewItemResult(newPageName, section, iconProvider.NewPage, OneNoteApp.CreatePage); + result.PreviewPanel = GetNewPagePreviewPanel(section, newPageName); + return result; + } - 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 => - { - if (!validTitle) - { - return false; - } - - bool showOneNote = !c.SpecialKeyState.CtrlPressed; - - OneNoteApplication.CreateNotebook(newNotebookName, showOneNote); - Main.ForceReQuery(); - - if (showOneNote) - WindowHelper.FocusOneNote(); + public Result CreateNewSectionResult(string newSectionName, INotebookOrSectionGroup parent) => CreateNewItemResult(newSectionName, parent, iconProvider.NewSection, OneNoteApp.CreateSection); + + 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 showOneNote; - }, - }; - } - public List ContextMenu(Result selectedResult) { var results = new List(); @@ -393,62 +294,81 @@ 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, + Title = $"Open \"{item.Name}\" in new OneNote window", + Icon = result.Icon, + Score = 20, + AddSelectedCount = false, Action = _ => { - OneNoteApplication.ComObject.NavigateTo(item.ID, fNewWindow: true); + OneNoteApp.Open(item, true); WindowHelper.FocusOneNote(); return true; } }); + - 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; + } + }); + + 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; } - 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 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)); + 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) { - 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, Section? section = null) { return new Result { @@ -460,8 +380,8 @@ Result NoItemsInCollectionResult(string title, string iconPath, string subTitle } } - private Lazy GetNewPagePreviewPanel(OneNoteSection section, string pageTitle) => - new(() => new NewOneNotePagePreviewPanel(context, section, pageTitle)); + private Lazy GetNewPagePreviewPanel(Section? section, string? pageTitle) + => new(() => new NewOneNotePagePreviewPanel(context, section, pageTitle)); public static List NoMatchesFound() { @@ -469,15 +389,21 @@ 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() + public List SearchType(string title, string? parentName, string subTitle = "") { - return SingleResult($"Now searching by title.", null, iconProvider.Search); + if (!string.IsNullOrWhiteSpace(parentName)) + { + title += $" in \"{parentName}\""; + } + return SingleResult(title, subTitle, iconProvider.Search); } private static List SingleResult(string title, string subTitle, string iconPath) @@ -492,6 +418,5 @@ private static List SingleResult(string title, string subTitle, string i } }; } - } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs b/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs new file mode 100644 index 0000000..0862435 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/DefaultSearch.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using OneNoteApp = LinqToOneNote.OneNote; + +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])) + { + return resultCreator.InvalidQuery(); + } + + return OneNoteApp.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..cdb1a26 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/NotebookExplorer.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LinqToOneNote; +using LinqToOneNote.Abstractions; +using OneNoteApp = LinqToOneNote.OneNote; + + +namespace Flow.Launcher.Plugin.OneNote.Search +{ + public class NotebookExplorer : SearchBase + { + private readonly 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; + } + }); + } + + internal List GetResults(Query query) + { + if (!ValidateSearch(query, out string? search, out IOneNoteItem? parent, out IEnumerable collection)) + return resultCreator.InvalidQuery(false); + + List results = search switch + { + { } 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), + }; + + if (parent == null) + return results; + + Result result = resultCreator.CreateOneNoteItemResult(parent, false, score: Result.MaxScore); + result.Title = $"Open \"{parent.Name}\" in OneNote"; + result.SubTitle = $"Use \'{Keywords.ScopedSearch}\' to search this item. Use \'{Keywords.TitleSearch}\' to search by title in this item"; + results.Add(result); + return results; + } + + 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; + 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; + + 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.Count != 0 ? results : resultCreator.EmptyCollection(results, parent); + } + + private List ScopedSearch(string query, IOneNoteItem parent) + { + if (query.Length == Keywords.ScopedSearch.Length) + return resultCreator.SearchType("Now searching all pages", parent.Name); + + if (!char.IsLetterOrDigit(query[Keywords.ScopedSearch.Length])) + return resultCreator.InvalidQuery(); + + string currentSearch = query[Keywords.TitleSearch.Length..]; + + var results = OneNoteApp.FindPages(currentSearch, parent) + .Select(pg => resultCreator.CreatePageResult(pg, currentSearch)) + .ToList(); + + return results.Count != 0 ? results : ResultCreator.NoMatchesFound(); + } + + 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 Section && 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 x: + results.Add(resultCreator.CreateNewSectionResult(search, x)); + results.Add(resultCreator.CreateNewSectionGroupResult(search, x)); + break; + case Section 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..6e6fcdf --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/RecentPages.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; +using LinqToOneNote; +using OneNoteApp = LinqToOneNote.OneNote; + +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; + + if (query.Length > keyword.Length && int.TryParse(query[keyword.Length..], out int userChosenCount)) + count = userChosenCount; + + 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/SearchBase.cs b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs new file mode 100644 index 0000000..cc54ff1 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchBase.cs @@ -0,0 +1,23 @@ +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; +#nullable disable + public readonly Keyword keyword; +#nullable restore + protected SearchBase(PluginInitContext context, Settings settings, ResultCreator resultCreator, Keyword? keyword) + { + this.context = context; + this.settings = settings; + this.resultCreator = resultCreator; + this.keyword = keyword; + } + 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..20ac402 --- /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 LinqToOneNote; + +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 Section 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..6581584 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/SearchManager.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; + +namespace Flow.Launcher.Plugin.OneNote.Search +{ + public class SearchManager + { + private readonly TitleSearch titleSearch; + private readonly NotebookExplorer notebookExplorer; + private readonly DefaultSearch defaultSearch; + private readonly RecentPages recentPages; + + public SearchManager(PluginInitContext context, Settings settings, ResultCreator resultCreator, VisibilityChanged visibilityChanged) + { + titleSearch = new TitleSearch(context, settings, resultCreator); + notebookExplorer = new NotebookExplorer(context, settings, resultCreator, titleSearch, visibilityChanged); + recentPages = new RecentPages(context, settings, resultCreator); + defaultSearch = new DefaultSearch(context, settings, resultCreator); + } + + 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(query), + { } when search.StartsWithOrd(recentPages.keyword) => recentPages.GetResults(search), + _ => defaultSearch.GetResults(search!), + }; + } + } +} \ 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..4c7101d --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Search/TitleSearch.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using LinqToOneNote; +using OneNoteApp = LinqToOneNote.OneNote; + +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 override List GetResults(string query) => Filter(query, null, OneNoteApp.GetFullHierarchy().Notebooks); + + public List Filter(string query, IOneNoteItem? parent, IEnumerable collection) + { + if (query.Length == keyword.Length) + return resultCreator.SearchType("Now searching by title", parent?.Name); + + var currentSearch = query[keyword.Length..]; + + var results = collection.Descendants() + .FilterBySettings(settings) + .FuzzySearch(currentSearch, context) + .Select(x => resultCreator.CreateOneNoteItemResult(x.Item, false, x.HighlightData, x.Score)) + .ToList(); + + return results.Count != 0 ? 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 c21b239..0000000 --- a/Flow.Launcher.Plugin.OneNote/SearchManager.NotebookExplorer.cs +++ /dev/null @@ -1,173 +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(); - - 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; - } - - 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; - } - } -} 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/DesignTimeStyles.xaml b/Flow.Launcher.Plugin.OneNote/UI/DesignTimeStyles.xaml new file mode 100644 index 0000000..2083af5 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/UI/DesignTimeStyles.xaml @@ -0,0 +1,291 @@ + + + + Off + On + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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/RelayCommand.cs b/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs index e462b62..41a51ae 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; @@ -6,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 { @@ -15,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; @@ -23,12 +22,33 @@ 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(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/Styles.xaml b/Flow.Launcher.Plugin.OneNote/UI/Styles.xaml index c5dc3ab..8846fe3 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/Styles.xaml +++ b/Flow.Launcher.Plugin.OneNote/UI/Styles.xaml @@ -2,7 +2,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=PresentationFramework" - xmlns:ui="http://schemas.modernwpf.com/2019"> + xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"> + + + + + +