diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index c957232..0000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "Odotocodot.OneNote.Linq"] - path = Odotocodot.OneNote.Linq - url = https://github.com/Odotocodot/Odotocodot.OneNote.Linq - branch = flow-plugin diff --git a/Changelog.md b/Changelog.md new file mode 100644 index 0000000..180487f --- /dev/null +++ b/Changelog.md @@ -0,0 +1,97 @@ +# Changelog + +## 2.1.0 - 2024-6-24 + +### Added + +- New and improved icons. +- New preview panel for creating pages ([#20](https://github.com/Odotocodot/Flow.Launcher.Plugin.OneNote/issues/20)) +- New setting for icon theme: FL Default (matches Flow Launcher's theme), light, dark and color. +- Opening a hierarchy item from the plugin now always brings OneNote to the front. +- New Hotkey (Ctrl + ⏎ Enter) to create new items without opening them in OneNote. + +### Changed + +- Refactored icon generation. +- Refactored settings view. +- Changed Linq2OneNote library reference from submodule to NuGet package. +- Updated tooltips for notebooks, section groups, sections and pages. + +### Fixes + +- Fixed incorrect autocomplete text for creating new items. + +## 2.0.1 - 2023-10-09 + +### Added + +- OneNote is now opened asynchronously to prevent blocking UI ([#15](https://github.com/Odotocodot/Flow.Launcher.Plugin.OneNote/issues/15)) + +## 2.0.0 - 2023-10-05 + +### **Breaking Changes** + +- Now requires Flow Launcher version 1.16 or later. + +### Added + +- **[Created custom OneNote parser/library](https://github.com/Odotocodot/Linq2OneNote)**, adding the ability for several new features. +- Support for section groups when using the notebook explorer. +- Support for displaying unread results. +- Support for showing locked sections in results (you still can't see inside them unless they are unlocked). +- The ability to search by only title. +- The ability to do a scoped search (e.g. search in one section only). +- **Settings!** You can change these options: + - Show unread icons. + - Show encrypted sections. + - Show recycle bin items. + - Created coloured icons for notebook and sections. + - Default number of recent pages + - **Customisable keywords!** + +### Changed + +- Compressed images. +- Reduced the calls to create a OneNote COM object, this should lead to an overall smoother experience. +- Updated to .NET 7 (update Flow Launcher if an error persists). +- Refactored the majority of code and project structure. + +### Removed + +- [Scipbe.Common.Office.OneNote](https://github.com/scipbe/ScipBe-Common-Office) package reference. + +## 1.1.1 - 2023-03-05 + +### Fixes + +- Fixed crash on search ([#7](https://github.com/Odotocodot/Flow.Launcher.Plugin.OneNote/issues/7)) + +## 1.1.0 - 2023-03-04 + +### Added + +- Added the ability to create notebooks, sections and pages. + +### Changes + +- Improved the readme + +### Fixes + +- Fixed typos + +## 1.0.2 - 2023-02-28 + +### Fixes + +- Fixed crash due to encrypted sections ([#3](https://github.com/Odotocodot/Flow.Launcher.Plugin.OneNote/issues/3), [#4](https://github.com/Odotocodot/Flow.Launcher.Plugin.OneNote/issues/4)) + +## 1.0.1 - 2023-01-15 + +### Fixes + +- Fixed crash on invalid search ([#1](https://github.com/Odotocodot/Flow.Launcher.Plugin.OneNote/issues/1)) + +## 1.0.0 - 2022-12-16 + +Initial Release diff --git a/Flow.Launcher.Plugin.OneNote.sln b/Flow.Launcher.Plugin.OneNote.sln index 7c6236a..35ab3f3 100644 --- a/Flow.Launcher.Plugin.OneNote.sln +++ b/Flow.Launcher.Plugin.OneNote.sln @@ -11,10 +11,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution LICENSE = LICENSE Readme.md = Readme.md release.ps1 = release.ps1 + Changelog.md = Changelog.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Odotocodot.OneNote.Linq", "Odotocodot.OneNote.Linq\Odotocodot.OneNote.Linq\Odotocodot.OneNote.Linq.csproj", "{AB2CEDD9-15DB-4EAF-A675-10928E83918D}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -25,10 +24,6 @@ Global {3801047C-BEF0-4774-91DB-B64EEE874BB2}.Debug|Any CPU.Build.0 = Debug|Any CPU {3801047C-BEF0-4774-91DB-B64EEE874BB2}.Release|Any CPU.ActiveCfg = Release|Any CPU {3801047C-BEF0-4774-91DB-B64EEE874BB2}.Release|Any CPU.Build.0 = Release|Any CPU - {AB2CEDD9-15DB-4EAF-A675-10928E83918D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AB2CEDD9-15DB-4EAF-A675-10928E83918D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AB2CEDD9-15DB-4EAF-A675-10928E83918D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AB2CEDD9-15DB-4EAF-A675-10928E83918D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj b/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj index d40de89..0633013 100644 --- a/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj +++ b/Flow.Launcher.Plugin.OneNote/Flow.Launcher.Plugin.OneNote.csproj @@ -11,6 +11,7 @@ false true true + true @@ -26,8 +27,10 @@ + - + + @@ -35,8 +38,4 @@ Always - - - - diff --git a/Flow.Launcher.Plugin.OneNote/Icons.cs b/Flow.Launcher.Plugin.OneNote/Icons.cs deleted file mode 100644 index e118d92..0000000 --- a/Flow.Launcher.Plugin.OneNote/Icons.cs +++ /dev/null @@ -1,123 +0,0 @@ -using Odotocodot.OneNote.Linq; -using System; -using System.IO; - -namespace Flow.Launcher.Plugin.OneNote -{ - public class Icons : BaseModel - { - public const string Logo = "Images/logo.png"; - public const string Sync = "Images/refresh.png"; - public const string Warning = "Images/warning.png"; - public const string Search = Logo; - public const string RecycleBin = "Images/recycle_bin.png"; - public const string Recent = "Images/recent.png"; - public const string RecentPage = "Images/recent_page.png"; - - public const string Page = Logo; - public const string Section = "Images/section.png"; - public const string SectionGroup = "Images/section_group.png"; - public const string Notebook = "Images/notebook.png"; - - public const string NewPage = "Images/new_page.png"; - public const string NewSection = "Images/new_section.png"; - public const string NewSectionGroup = "Images/new_section_group.png"; - public const string NewNotebook = "Images/new_notebook.png"; - - private OneNoteItemIcons notebookIcons; - private OneNoteItemIcons sectionIcons; - private Settings settings; - - public int CachedIconCount => notebookIcons.IconCount + sectionIcons.IconCount; - public string CachedIconsFileSize => GetBytesReadable(notebookIcons.IconsFileSize + sectionIcons.IconsFileSize); - public static string NotebookIconDirectory { get; private set; } - public static string SectionIconDirectory { get; private set; } - - - private static readonly Lazy lazy = new(); - public static Icons Instance => lazy.Value; - - public static void Init(PluginInitContext context, Settings settings) - { - NotebookIconDirectory = Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "Images", "NotebookIcons"); - SectionIconDirectory = Path.Combine(context.CurrentPluginMetadata.PluginDirectory, "Images", "SectionIcons"); - - Instance.notebookIcons = new OneNoteItemIcons(NotebookIconDirectory, Path.Combine(context.CurrentPluginMetadata.PluginDirectory, Notebook)); - Instance.sectionIcons = new OneNoteItemIcons(SectionIconDirectory, Path.Combine(context.CurrentPluginMetadata.PluginDirectory, Section)); - - - Instance.notebookIcons.PropertyChanged += Instance.IconCountChanged; - Instance.sectionIcons.PropertyChanged += Instance.IconCountChanged; - - Instance.settings = settings; - } - - public static void Close() - { - Instance.notebookIcons.PropertyChanged -= Instance.IconCountChanged; - Instance.sectionIcons.PropertyChanged -= Instance.IconCountChanged; - } - private void IconCountChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) - { - OnPropertyChanged(nameof(CachedIconCount)); - OnPropertyChanged(nameof(CachedIconsFileSize)); - } - - public static string GetIcon(IOneNoteItem item) - { - return item switch - { - OneNoteNotebook notebook => Instance.settings.CreateColoredIcons && notebook.Color.HasValue - ? Instance.notebookIcons.GetIcon(notebook.Color.Value) - : Notebook, - OneNoteSectionGroup sectionGroup => sectionGroup.IsRecycleBin - ? RecycleBin - : SectionGroup, - OneNoteSection section => Instance.settings.CreateColoredIcons && section.Color.HasValue - ? Instance.sectionIcons.GetIcon(section.Color.Value) - : Section, - OneNotePage => Page, - _ => Warning, - }; - } - - public void ClearCachedIcons() - { - notebookIcons.ClearCachedIcons(); - sectionIcons.ClearCachedIcons(); - } - - // Returns the human-readable file size for an arbitrary, 64-bit file size - // The default format is "0.### XB", e.g. "4.2 KB" or "1.434 GB" - private static string GetBytesReadable(long i) - { - // Get absolute value - long absolute_i = Math.Abs(i); - // Determine the suffix and readable value - string suffix; - double readable; - switch (absolute_i) - { - case >= 0x40000000: // Gigabyte - suffix = "GB"; - readable = i >> 20; - break; - case >= 0x100000: // Megabyte - suffix = "MB"; - readable = i >> 10; - break; - case >= 0x400: - suffix = "KB"; // Kilobyte - readable = i; - break; - default: - return i.ToString("0 B"); // Byte - } - // Divide by 1024 to get fractional value - readable /= 1024; - // Return formatted number with suffix - return readable.ToString("0.## ") + suffix; - } - - } -} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Icons/IconConstants.cs b/Flow.Launcher.Plugin.OneNote/Icons/IconConstants.cs new file mode 100644 index 0000000..8dd61b1 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Icons/IconConstants.cs @@ -0,0 +1,26 @@ +namespace Flow.Launcher.Plugin.OneNote.Icons +{ + public static class IconConstants + { + public const string ImagesDirectory = "Images/"; + public const string GeneratedImagesDirectory = $"{ImagesDirectory}Generated/"; + + public const string Logo = "logo"; + + public const string Notebook = "notebook"; + public const string SectionGroup = "section_group"; + public const string RecycleBin = "recycle_bin"; + public const string Section = "section"; + public const string Page = "page"; + + public const string Sync = "sync"; + public const string Search = "search"; + public const string Recent = "page_recent"; + public const string NotebookExplorer = "notebook_explorer"; + public const string NewPage = "page_new"; + public const string NewSection = "section_new"; + public const string NewSectionGroup = "section_group_new"; + public const string NewNotebook = "notebook_new"; + public const string Warning = "warning"; + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs b/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs new file mode 100644 index 0000000..dfe0df2 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs @@ -0,0 +1,30 @@ +using System.Drawing; +using Odotocodot.OneNote.Linq; + +namespace Flow.Launcher.Plugin.OneNote.Icons +{ + public record struct IconGeneratorInfo + { + public string Prefix { get; } + public Color? Color { get; } + + public IconGeneratorInfo(OneNoteNotebook notebook) + { + 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; + } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs b/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs new file mode 100644 index 0000000..2e12698 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Color = System.Drawing.Color; +using IC = Flow.Launcher.Plugin.OneNote.Icons.IconConstants; + +namespace Flow.Launcher.Plugin.OneNote.Icons +{ + public class IconProvider : BaseModel + { + public const string Logo = IC.ImagesDirectory + IC.Logo + ".png"; + public string Sync => GetIconPath(IC.Sync); + public string Search => GetIconPath(IC.Search); + public string Recent => GetIconPath(IC.Recent); + public string NotebookExplorer => GetIconPath(IC.NotebookExplorer); + public string QuickNote => NewPage; + public string NewPage => GetIconPath(IC.NewPage); + public string NewSection => GetIconPath(IC.NewSection); + 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; + + public DirectoryInfo GeneratedImagesDirectoryInfo { get; } + public int CachedIconCount => iconCache.Keys.Count(k => char.IsDigit(k.Split('.')[1][1])); + + private readonly PluginInitContext context; + + public IconProvider(PluginInitContext context, Settings settings) + { + imagesDirectory = $"{context.CurrentPluginMetadata.PluginDirectory}/{IC.ImagesDirectory}"; + + GeneratedImagesDirectoryInfo = Directory.CreateDirectory($"{context.CurrentPluginMetadata.PluginDirectory}/{IC.GeneratedImagesDirectory}"); + + this.context = context; + this.settings = settings; + + foreach (var image in GeneratedImagesDirectoryInfo.EnumerateFiles()) + { + var imageSource = BitmapImageFromPath(image.FullName); + imageSource.Freeze(); + iconCache.TryAdd(image.Name, imageSource); + } + } + + private string GetIconPath(string icon) => $"{IC.ImagesDirectory}{icon}.{GetIconThemeString(settings.IconTheme)}.png"; + + private static string GetIconThemeString(IconTheme iconTheme) + { + if (iconTheme == IconTheme.System) + { + iconTheme = FlowLauncherThemeToIconTheme(); + } + return Enum.GetName(iconTheme).ToLower(); + } + + private static IconTheme FlowLauncherThemeToIconTheme() + { + var color05B = (SolidColorBrush)Application.Current.TryFindResource("Color05B"); //Alt key "SystemControlPageTextBaseHighBrush" + if (color05B == null) + return IconTheme.Light; + + var color = color05B.Color; + return 5 * color.G + 2 * color.R + color.B > 8 * 128 //Is the color light? + ? IconTheme.Light + : IconTheme.Dark; + } + + private static BitmapImage BitmapImageFromPath(string path) => new BitmapImage(new Uri(path)); + + public Result.IconDelegate GetIcon(IconGeneratorInfo info) + { + bool generate = (string.CompareOrdinal(info.Prefix, IC.Notebook) == 0 + || string.CompareOrdinal(info.Prefix, IC.Section) == 0) + && settings.CreateColoredIcons + && info.Color.HasValue; + + return generate ? GetIconGenerated : GetIconStatic; + + ImageSource GetIconGenerated() + { + var imageSource = iconCache.GetOrAdd($"{info.Prefix}.{info.Color!.Value.ToArgb()}.png", ImageSourceFactory, info.Color.Value); + 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); + }); + } + } + + private ImageSource ImageSourceFactory(string key, Color color) + { + var prefix = key.Split('.')[0]; + var path = Path.Combine(imagesDirectory, $"{prefix}.dark.png"); + var bitmap = BitmapImageFromPath(path); + var newBitmap = ChangeIconColor(bitmap, color); + + path = $"{GeneratedImagesDirectoryInfo.FullName}{key}"; + + using var fileStream = new FileStream(path, FileMode.Create); + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(newBitmap)); + encoder.Save(fileStream); + return newBitmap; + } + + private static BitmapSource ChangeIconColor(BitmapImage bitmapImage, Color color) + { + var writeableBitmap = new WriteableBitmap(bitmapImage); + + var stride = writeableBitmap.BackBufferStride; + var pixelHeight = writeableBitmap.PixelHeight; + + var pixelData = new byte[stride * pixelHeight]; + var bytesPerPixel = writeableBitmap.Format.BitsPerPixel / 8; + + writeableBitmap.CopyPixels(pixelData, stride, 0); + for (int j = 0; j < pixelHeight; j++) + { + int line = j * stride; + for (int i = 0; i < stride; i += bytesPerPixel) + { + pixelData[line + i] = color.B; + pixelData[line + i + 1] = color.G; + pixelData[line + i + 2] = color.R; + } + } + writeableBitmap.WritePixels(new Int32Rect(0, 0, writeableBitmap.PixelWidth, pixelHeight), + pixelData, stride, 0); + writeableBitmap.Freeze(); + + return writeableBitmap; + } + + public void ClearCachedIcons() + { + iconCache.Clear(); + foreach (var file in GeneratedImagesDirectoryInfo.EnumerateFiles()) + { + try + { + file.Delete(); + } + catch (Exception e) + { + // context.API.ShowMsg("Failed to delete", $"Failed to delete {file.Name}"); + context.API.LogException(nameof(IconProvider), "Failed to delete", e); + } + + } + OnPropertyChanged(nameof(CachedIconCount)); + } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/Icons/IconTheme.cs b/Flow.Launcher.Plugin.OneNote/Icons/IconTheme.cs new file mode 100644 index 0000000..38db369 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/Icons/IconTheme.cs @@ -0,0 +1,11 @@ +namespace Flow.Launcher.Plugin.OneNote.Icons +{ + public enum IconTheme + { + System, + Light, + Dark, + Color + } +} + diff --git a/Flow.Launcher.Plugin.OneNote/Images/new_notebook.png b/Flow.Launcher.Plugin.OneNote/Images/new_notebook.png deleted file mode 100644 index 6eef320..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/new_notebook.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/new_page.png b/Flow.Launcher.Plugin.OneNote/Images/new_page.png deleted file mode 100644 index aee4a9b..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/new_page.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/new_section.png b/Flow.Launcher.Plugin.OneNote/Images/new_section.png deleted file mode 100644 index 0e46a95..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/new_section.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/new_section_group.png b/Flow.Launcher.Plugin.OneNote/Images/new_section_group.png deleted file mode 100644 index 7219366..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/new_section_group.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/notebook.color.png b/Flow.Launcher.Plugin.OneNote/Images/notebook.color.png new file mode 100644 index 0000000..41c09e0 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/notebook.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/notebook.dark.png b/Flow.Launcher.Plugin.OneNote/Images/notebook.dark.png new file mode 100644 index 0000000..243267b Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/notebook.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/notebook.light.png b/Flow.Launcher.Plugin.OneNote/Images/notebook.light.png new file mode 100644 index 0000000..0b7aa0f Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/notebook.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/notebook.png b/Flow.Launcher.Plugin.OneNote/Images/notebook.png deleted file mode 100644 index b9f296d..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/notebook.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/notebook_explorer.color.png b/Flow.Launcher.Plugin.OneNote/Images/notebook_explorer.color.png new file mode 100644 index 0000000..50b77c2 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/notebook_explorer.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/notebook_explorer.dark.png b/Flow.Launcher.Plugin.OneNote/Images/notebook_explorer.dark.png new file mode 100644 index 0000000..ccf8eae Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/notebook_explorer.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/notebook_explorer.light.png b/Flow.Launcher.Plugin.OneNote/Images/notebook_explorer.light.png new file mode 100644 index 0000000..1cc20b2 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/notebook_explorer.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/notebook_new.color.png b/Flow.Launcher.Plugin.OneNote/Images/notebook_new.color.png new file mode 100644 index 0000000..184e666 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/notebook_new.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/notebook_new.dark.png b/Flow.Launcher.Plugin.OneNote/Images/notebook_new.dark.png new file mode 100644 index 0000000..044399e Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/notebook_new.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/notebook_new.light.png b/Flow.Launcher.Plugin.OneNote/Images/notebook_new.light.png new file mode 100644 index 0000000..7e45f31 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/notebook_new.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/page.color.png b/Flow.Launcher.Plugin.OneNote/Images/page.color.png new file mode 100644 index 0000000..475f806 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/page.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/page.dark.png b/Flow.Launcher.Plugin.OneNote/Images/page.dark.png new file mode 100644 index 0000000..b6ab349 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/page.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/page.light.png b/Flow.Launcher.Plugin.OneNote/Images/page.light.png new file mode 100644 index 0000000..d1e4c39 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/page.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/page_new.color.png b/Flow.Launcher.Plugin.OneNote/Images/page_new.color.png new file mode 100644 index 0000000..bd209be Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/page_new.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/page_new.dark.png b/Flow.Launcher.Plugin.OneNote/Images/page_new.dark.png new file mode 100644 index 0000000..990c51d Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/page_new.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/page_new.light.png b/Flow.Launcher.Plugin.OneNote/Images/page_new.light.png new file mode 100644 index 0000000..01400e4 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/page_new.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/page_recent.color.png b/Flow.Launcher.Plugin.OneNote/Images/page_recent.color.png new file mode 100644 index 0000000..ed53432 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/page_recent.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/page_recent.dark.png b/Flow.Launcher.Plugin.OneNote/Images/page_recent.dark.png new file mode 100644 index 0000000..68c9ea5 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/page_recent.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/page_recent.light.png b/Flow.Launcher.Plugin.OneNote/Images/page_recent.light.png new file mode 100644 index 0000000..400e969 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/page_recent.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/recent.png b/Flow.Launcher.Plugin.OneNote/Images/recent.png deleted file mode 100644 index ee97336..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/recent.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/recent_page.png b/Flow.Launcher.Plugin.OneNote/Images/recent_page.png deleted file mode 100644 index 9f7a5de..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/recent_page.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.color.png b/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.color.png new file mode 100644 index 0000000..b79e0a8 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.dark.png b/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.dark.png new file mode 100644 index 0000000..080cee6 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.light.png b/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.light.png new file mode 100644 index 0000000..b032988 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.png b/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.png deleted file mode 100644 index 879d0e0..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/recycle_bin.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/refresh.png b/Flow.Launcher.Plugin.OneNote/Images/refresh.png deleted file mode 100644 index 98e70e3..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/refresh.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/search.color.png b/Flow.Launcher.Plugin.OneNote/Images/search.color.png new file mode 100644 index 0000000..a8c3c76 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/search.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/search.dark.png b/Flow.Launcher.Plugin.OneNote/Images/search.dark.png new file mode 100644 index 0000000..b960dea Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/search.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/search.light.png b/Flow.Launcher.Plugin.OneNote/Images/search.light.png new file mode 100644 index 0000000..c64d7cd Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/search.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section.color.png b/Flow.Launcher.Plugin.OneNote/Images/section.color.png new file mode 100644 index 0000000..2c5e320 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section.dark.png b/Flow.Launcher.Plugin.OneNote/Images/section.dark.png new file mode 100644 index 0000000..f10d2cd Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section.light.png b/Flow.Launcher.Plugin.OneNote/Images/section.light.png new file mode 100644 index 0000000..f2939d9 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section.png b/Flow.Launcher.Plugin.OneNote/Images/section.png deleted file mode 100644 index e336883..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/section.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section_group.color.png b/Flow.Launcher.Plugin.OneNote/Images/section_group.color.png new file mode 100644 index 0000000..96586e9 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section_group.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section_group.dark.png b/Flow.Launcher.Plugin.OneNote/Images/section_group.dark.png new file mode 100644 index 0000000..c20ca77 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section_group.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section_group.light.png b/Flow.Launcher.Plugin.OneNote/Images/section_group.light.png new file mode 100644 index 0000000..4abcbb7 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section_group.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section_group.png b/Flow.Launcher.Plugin.OneNote/Images/section_group.png deleted file mode 100644 index 9859d55..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/section_group.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section_group_new.color.png b/Flow.Launcher.Plugin.OneNote/Images/section_group_new.color.png new file mode 100644 index 0000000..3ab0ac7 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section_group_new.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section_group_new.dark.png b/Flow.Launcher.Plugin.OneNote/Images/section_group_new.dark.png new file mode 100644 index 0000000..7275038 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section_group_new.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section_group_new.light.png b/Flow.Launcher.Plugin.OneNote/Images/section_group_new.light.png new file mode 100644 index 0000000..ef4059b Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section_group_new.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section_new.color.png b/Flow.Launcher.Plugin.OneNote/Images/section_new.color.png new file mode 100644 index 0000000..4d9edb8 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section_new.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section_new.dark.png b/Flow.Launcher.Plugin.OneNote/Images/section_new.dark.png new file mode 100644 index 0000000..193ba21 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section_new.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/section_new.light.png b/Flow.Launcher.Plugin.OneNote/Images/section_new.light.png new file mode 100644 index 0000000..4e172bf Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/section_new.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/sync.color.png b/Flow.Launcher.Plugin.OneNote/Images/sync.color.png new file mode 100644 index 0000000..e09b7a6 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/sync.color.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/sync.dark.png b/Flow.Launcher.Plugin.OneNote/Images/sync.dark.png new file mode 100644 index 0000000..30d68e5 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/sync.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/sync.light.png b/Flow.Launcher.Plugin.OneNote/Images/sync.light.png new file mode 100644 index 0000000..39a3861 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/sync.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/warning.dark.png b/Flow.Launcher.Plugin.OneNote/Images/warning.dark.png new file mode 100644 index 0000000..017f2a0 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/warning.dark.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/warning.light.png b/Flow.Launcher.Plugin.OneNote/Images/warning.light.png new file mode 100644 index 0000000..ab87991 Binary files /dev/null and b/Flow.Launcher.Plugin.OneNote/Images/warning.light.png differ diff --git a/Flow.Launcher.Plugin.OneNote/Images/warning.png b/Flow.Launcher.Plugin.OneNote/Images/warning.png deleted file mode 100644 index da4f218..0000000 Binary files a/Flow.Launcher.Plugin.OneNote/Images/warning.png and /dev/null differ diff --git a/Flow.Launcher.Plugin.OneNote/Main.cs b/Flow.Launcher.Plugin.OneNote/Main.cs index 1e4f456..4bdb0b8 100644 --- a/Flow.Launcher.Plugin.OneNote/Main.cs +++ b/Flow.Launcher.Plugin.OneNote/Main.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.Windows.Controls; +using Flow.Launcher.Plugin.OneNote.Icons; +using Flow.Launcher.Plugin.OneNote.UI.Views; using Odotocodot.OneNote.Linq; namespace Flow.Launcher.Plugin.OneNote { @@ -9,18 +12,25 @@ public class Main : IAsyncPlugin, IContextMenu, ISettingProvider, IDisposable { private PluginInitContext context; + private ResultCreator resultCreator; private SearchManager searchManager; private Settings settings; + private IconProvider iconProvider; private static SemaphoreSlim semaphore; + private static Main instance; + + private Query currentQuery; public Task InitAsync(PluginInitContext context) { this.context = context; settings = context.API.LoadSettingJsonStorage(); - Icons.Init(context, settings); - searchManager = new SearchManager(context, settings, new ResultCreator(context, settings)); + 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; return Task.CompletedTask; } @@ -38,42 +48,38 @@ private static async Task OneNoteInitAsync(CancellationToken token = default) return; await semaphore.WaitAsync(token); - OneNoteApplication.Init(); + OneNoteApplication.InitComObject(); semaphore.Release(); } public async Task> QueryAsync(Query query, CancellationToken token) { + currentQuery = query; var init = OneNoteInitAsync(token); if (string.IsNullOrEmpty(query.Search)) - return searchManager.EmptyQuery(); + return resultCreator.EmptyQuery(); await init; - return query.FirstSearch switch - { - string fs when fs.StartsWith(settings.Keywords.RecentPages) => searchManager.RecentPages(fs), - string fs when fs.StartsWith(settings.Keywords.NotebookExplorer) => searchManager.NotebookExplorer(query), - string fs when fs.StartsWith(settings.Keywords.TitleSearch) => searchManager.TitleSearch(string.Join(' ', query.SearchTerms), OneNoteApplication.GetNotebooks()), - _ => searchManager.DefaultSearch(query.Search) - }; + return searchManager.Query(query); } + public static void ForceReQuery() => instance.context.API.ChangeQuery(instance.currentQuery.RawQuery, true); + public List LoadContextMenus(Result selectedResult) { - return searchManager.ContextMenu(selectedResult); + return resultCreator.ContextMenu(selectedResult); } - public System.Windows.Controls.Control CreateSettingPanel() + public Control CreateSettingPanel() { - return new UI.Views.SettingsView(new UI.ViewModels.SettingsViewModel(context, settings)); + return new SettingsView(context, settings, iconProvider); } public void Dispose() { context.API.VisibilityChanged -= OnVisibilityChanged; semaphore.Dispose(); - Icons.Close(); OneNoteApplication.ReleaseComObject(); } } diff --git a/Flow.Launcher.Plugin.OneNote/OneNoteItemIcons.cs b/Flow.Launcher.Plugin.OneNote/OneNoteItemIcons.cs deleted file mode 100644 index a4994da..0000000 --- a/Flow.Launcher.Plugin.OneNote/OneNoteItemIcons.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; - -namespace Flow.Launcher.Plugin.OneNote -{ - public class OneNoteItemIcons : BaseModel - { - private readonly Dictionary icons; - private readonly string iconDirectory; - private readonly string baseIconPath; - - public OneNoteItemIcons(string folderPath, string baseIconPath) - { - icons = new Dictionary(); - iconDirectory = folderPath; - this.baseIconPath = baseIconPath; - - Directory.CreateDirectory(iconDirectory); - foreach (var imagePath in Directory.EnumerateFiles(iconDirectory)) - { - if (int.TryParse(Path.GetFileNameWithoutExtension(imagePath), out int argb)) - icons.Add(Color.FromArgb(argb), imagePath); - } - } - public int IconCount => icons.Count; - - public long IconsFileSize => new DirectoryInfo(iconDirectory).EnumerateFiles() - .Select(file => file.Length) - .Aggregate(0L, (a, b) => a + b); - public void ClearCachedIcons() - { - icons.Clear(); - foreach (var img in new DirectoryInfo(iconDirectory).EnumerateFiles()) - { - img.Delete(); - } - OnPropertyChanged(nameof(IconCount)); - OnPropertyChanged(nameof(IconsFileSize)); - } - - public string GetIcon(Color color) - { - if (!icons.TryGetValue(color, out string path)) - { - //Create Colored Image - using var bitmap = new Bitmap(baseIconPath); - BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat); - - int bytesPerPixel = Bitmap.GetPixelFormatSize(bitmap.PixelFormat) / 8; - byte[] pixels = new byte[bitmapData.Stride * bitmap.Height]; - IntPtr pointer = bitmapData.Scan0; - Marshal.Copy(pointer, pixels, 0, pixels.Length); - int bytesWidth = bitmapData.Width * bytesPerPixel; - - for (int j = 0; j < bitmapData.Height; j++) - { - int line = j * bitmapData.Stride; - for (int i = 0; i < bytesWidth; i += bytesPerPixel) - { - pixels[line + i] = color.B; - pixels[line + i + 1] = color.G; - pixels[line + i + 2] = color.R; - } - } - - Marshal.Copy(pixels, 0, pointer, pixels.Length); - bitmap.UnlockBits(bitmapData); - path = Path.Combine(iconDirectory, color.ToArgb() + ".png"); - bitmap.Save(path, ImageFormat.Png); - - icons.Add(color, path); - OnPropertyChanged(nameof(IconCount)); - OnPropertyChanged(nameof(IconsFileSize)); - - } - - return path; - } - } -} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs index 5c9258b..b2d17aa 100644 --- a/Flow.Launcher.Plugin.OneNote/ResultCreator.cs +++ b/Flow.Launcher.Plugin.OneNote/ResultCreator.cs @@ -1,6 +1,11 @@ +using System; 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; namespace Flow.Launcher.Plugin.OneNote @@ -9,253 +14,461 @@ public class ResultCreator { private readonly PluginInitContext context; private readonly Settings settings; + private readonly IconProvider iconProvider; private const string PathSeparator = " > "; + private const string BulletPoint = "\u2022 "; + private const string TrianglePoint = "\u2023 "; - public ResultCreator(PluginInitContext context, Settings settings) + private string ActionKeyword => context.CurrentPluginMetadata.ActionKeyword; + public ResultCreator(PluginInitContext context, Settings settings, IconProvider iconProvider) { this.settings = settings; + this.iconProvider = iconProvider; this.context = context; } - private static string GetNicePath(IOneNoteItem item, bool includeSelf = true, string separator = PathSeparator) - { - return item.RelativePath.Replace(OneNoteParser.RelativePathSeparator.ToString(), separator); - } + private static string GetNicePath(IOneNoteItem item, string separator = PathSeparator) => + item.RelativePath.Replace(OneNoteApplication.RelativePathSeparator.ToString(), separator); private string GetTitle(IOneNoteItem item, List highlightData) { string title = item.Name; - if (item.IsUnread && settings.ShowUnread) - { - string unread = "\u2022 "; - title = title.Insert(0, unread); + if (!item.IsUnread || !settings.ShowUnread) + return title; + + title = title.Insert(0, BulletPoint); - if (highlightData != null) - { - for (int i = 0; i < highlightData.Count; i++) - { - highlightData[i] += unread.Length; - } - } + if (highlightData == null) + return title; + + for (int i = 0; i < highlightData.Count; i++) + { + highlightData[i] += BulletPoint.Length; } return title; } + + private string GetAutoCompleteText(IOneNoteItem item) + => $"{ActionKeyword} {settings.Keywords.NotebookExplorer}{GetNicePath(item, Keywords.NotebookExplorerSeparator)}{Keywords.NotebookExplorerSeparator}"; + - #region Create OneNote Item Results - public Result CreatePageResult(OneNotePage page, string query = null) + public List EmptyQuery() { - return CreateOneNoteItemResult(page, false, string.IsNullOrWhiteSpace(query) ? null : context.API.FuzzySearch(query, page.Name).MatchData); - } + return new List + { + new Result + { + Title = "Search OneNote pages", + SubTitle = "Try typing something!", + AutoCompleteText = ActionKeyword, + IcoPath = iconProvider.Search, + Score = 5000, + }, + new Result + { + Title = "View notebook explorer", + SubTitle = $"Type \"{settings.Keywords.NotebookExplorer}\" or select this option to search by notebook structure ", + AutoCompleteText = $"{ActionKeyword} {settings.Keywords.NotebookExplorer}", + IcoPath = iconProvider.NotebookExplorer, + Score = 2000, + Action = _ => + { + context.API.ChangeQuery($"{ActionKeyword} {settings.Keywords.NotebookExplorer}", true); + return false; + }, + }, + new Result + { + Title = "See recent pages", + SubTitle = $"Type \"{settings.Keywords.RecentPages}\" or select this option to see recently modified pages", + AutoCompleteText = $"{ActionKeyword} {settings.Keywords.RecentPages}", + IcoPath = iconProvider.Recent, + Score = -1000, + Action = _ => + { + context.API.ChangeQuery($"{ActionKeyword} {settings.Keywords.RecentPages}", true); + return false; + }, + }, + new Result + { + Title = "New quick note", + IcoPath = iconProvider.QuickNote, + Score = -4000, + PreviewPanel = GetNewPagePreviewPanel(null, null), + Action = _ => + { + OneNoteApplication.CreateQuickNote(true); + WindowHelper.FocusOneNote(); + return true; + }, + }, + new Result + { + Title = "Open and sync notebooks", + IcoPath = iconProvider.Sync, + Score = int.MinValue, + Action = _ => + { + var notebooks = OneNoteApplication.GetNotebooks(); + foreach (var notebook in notebooks) + { + notebook.Sync(); + } + notebooks.GetPages() + .Where(i => !i.IsInRecycleBin) + .OrderByDescending(pg => pg.LastModified) + .First() + .OpenInOneNote(); + + WindowHelper.FocusOneNote(); + return true; + }, + }, + }; + } + public Result CreateOneNoteItemResult(IOneNoteItem item, bool actionIsAutoComplete, List highlightData = null, int score = 0) { string title = GetTitle(item, highlightData); - string titleToolTip = null; - string subTitle = GetNicePath(item, true); - string subTitleToolTip = null; - string autoCompleteText = $"{context.CurrentPluginMetadata.ActionKeyword} {settings.Keywords.NotebookExplorer}{GetNicePath(item, true, Keywords.NotebookExplorerSeparator)}"; + string toolTip = string.Empty; + string subTitle = GetNicePath(item); + string autoCompleteText = GetAutoCompleteText(item); + IconGeneratorInfo iconInfo; + switch (item) { case OneNoteNotebook notebook: - titleToolTip = $"{notebook.Name}\n\n" + - $"Last Modified:\t{notebook.LastModified:F}\n" + - $"Sections:\t\t{notebook.Sections.Count()}\n" + - $"Sections Groups:\t{notebook.SectionGroups.Count()}"; + 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())} + """; subTitle = string.Empty; - autoCompleteText += Keywords.NotebookExplorerSeparator; + iconInfo = new IconGeneratorInfo(notebook); break; case OneNoteSectionGroup sectionGroup: - subTitleToolTip = $"{subTitle}\n\n" + - $"Last Modified:\t{sectionGroup.LastModified:F}\n" + - $"Sections:\t\t{sectionGroup.Sections.Count()}\n" + - $"Sections Groups:\t{sectionGroup.SectionGroups.Count()}"; + 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())} + """; - autoCompleteText += Keywords.NotebookExplorerSeparator; + iconInfo = new IconGeneratorInfo(sectionGroup); break; case OneNoteSection section: if (section.Encrypted) { - title += " [Encrypted]"; - if (section.Locked) - title += "[Locked]"; - else - title += "[Unlocked]"; + title += $" [Encrypted] {(section.Locked ? "[Locked]" : "[Unlocked]")}"; } - subTitleToolTip = $"{subTitle}\n\n" + - $"Last Modified:\t{section.LastModified}\n" + - $"Pages:\t\t{section.Pages.Count()}"; - - autoCompleteText += Keywords.NotebookExplorerSeparator; + toolTip = + $""" + Last Modified: + {TrianglePoint}{section.LastModified:F} + + Contains: + {TrianglePoint}{"page".ToQuantity(section.GetPages().Count())} + """; + + iconInfo = new IconGeneratorInfo(section); break; case OneNotePage page: + autoCompleteText = actionIsAutoComplete ? autoCompleteText[..^1] : string.Empty; + actionIsAutoComplete = false; - subTitle = subTitle.Remove(subTitle.Length - (page.Name.Length + PathSeparator.Length)); - subTitleToolTip = $"{subTitle}\n\n" + - $"Created:\t\t{page.Created:F}\n" + - $"Last Modified:\t{page.LastModified:F}"; + + subTitle = subTitle[..^(page.Name.Length + PathSeparator.Length)]; + toolTip = + $"Created:\t\t{page.Created:F}\n" + + $"Last Modified:\t{page.LastModified:F}"; + + iconInfo = new IconGeneratorInfo(page); + break; + default: + iconInfo = default; break; } + return new Result { Title = title, - TitleToolTip = titleToolTip, + TitleToolTip = toolTip, TitleHighlightData = highlightData, - SubTitle = subTitle, - SubTitleToolTip = subTitleToolTip, AutoCompleteText = autoCompleteText, + SubTitle = subTitle, Score = score, - IcoPath = Icons.GetIcon(item), + Icon = iconProvider.GetIcon(iconInfo), ContextData = item, - AsyncAction = async c => + AsyncAction = async _ => { if (actionIsAutoComplete) { - context.API.ChangeQuery(autoCompleteText); + context.API.ChangeQuery($"{autoCompleteText}", true); return false; } + await Task.Run(() => { - OneNoteApplication.SyncItem(item); - OneNoteApplication.OpenInOneNote(item); + item.Sync(); + item.OpenInOneNote(); }); + WindowHelper.FocusOneNote(); return true; - } + }, }; } - #endregion + + public Result CreatePageResult(OneNotePage page, string query) + => CreateOneNoteItemResult(page, false, string.IsNullOrWhiteSpace(query) ? null : context.API.FuzzySearch(query, page.Name).MatchData); - #region Create New OneNote Item Results - public static Result CreateNewPageResult(string pageTitle, OneNoteSection section) + public Result CreateRecentPageResult(OneNotePage page) { - pageTitle = pageTitle.Trim(); + var result = CreateOneNoteItemResult(page, false, null); + result.SubTitle = $"{page.LastModified.Humanize()}\t{result.SubTitle}"; + result.IcoPath = iconProvider.Recent; + return result; + } + + public Result CreateNewPageResult(string newPageName, OneNoteSection section) + { + newPageName = newPageName.Trim(); return new Result { - Title = $"Create page: \"{pageTitle}\"", - SubTitle = $"Path: {GetNicePath(section, true)} > {pageTitle}", - IcoPath = Icons.NewPage, + Title = $"Create page: \"{newPageName}\"", + SubTitle = $"Path: {GetNicePath(section)}{PathSeparator}{newPageName}", + AutoCompleteText = $"{GetAutoCompleteText(section)}{newPageName}", + IcoPath = iconProvider.NewPage, + PreviewPanel = GetNewPagePreviewPanel(section, newPageName), Action = c => { - OneNoteApplication.CreatePage(section, pageTitle); - return true; - } + bool showOneNote = !c.SpecialKeyState.CtrlPressed; + + OneNoteApplication.CreatePage(section, newPageName, showOneNote); + Main.ForceReQuery(); + + if(showOneNote) + WindowHelper.FocusOneNote(); + + return showOneNote; + }, }; } - public Result CreateNewSectionResult(string sectionTitle, IOneNoteItem parent) + public Result CreateNewSectionResult(string newSectionName, IOneNoteItem parent) { - sectionTitle = sectionTitle.Trim(); - bool validTitle = OneNoteParser.IsSectionNameValid(sectionTitle); + newSectionName = newSectionName.Trim(); + bool validTitle = OneNoteApplication.IsSectionNameValid(newSectionName); return new Result { - Title = $"Create section: \"{sectionTitle}\"", + Title = $"Create section: \"{newSectionName}\"", SubTitle = validTitle - ? $"Path: {GetNicePath(parent, true)} > {sectionTitle}" - : $"Section names cannot contain: {string.Join(' ', OneNoteParser.InvalidSectionChars)}", - IcoPath = Icons.NewSection, + ? $"Path: {GetNicePath(parent)}{PathSeparator}{newSectionName}" + : $"Section names cannot contain: {string.Join(' ', OneNoteApplication.InvalidSectionChars)}", + AutoCompleteText = $"{GetAutoCompleteText(parent)}{newSectionName}", + IcoPath = iconProvider.NewSection, Action = c => { if (!validTitle) + { return false; + } + bool showOneNote = !c.SpecialKeyState.CtrlPressed; + switch (parent) { case OneNoteNotebook notebook: - OneNoteApplication.CreateSection(notebook, sectionTitle); + OneNoteApplication.CreateSection(notebook, newSectionName, showOneNote); break; case OneNoteSectionGroup sectionGroup: - OneNoteApplication.CreateSection(sectionGroup, sectionTitle); - break; - default: + OneNoteApplication.CreateSection(sectionGroup, newSectionName, showOneNote); break; } - - context.API.ChangeQuery(context.CurrentPluginMetadata.ActionKeyword); - return true; - - } + + Main.ForceReQuery(); + if(showOneNote) + WindowHelper.FocusOneNote(); + + return showOneNote; + }, }; } - public Result CreateNewSectionGroupResult(string sectionGroupTitle, IOneNoteItem parent) + + public Result CreateNewSectionGroupResult(string newSectionGroupName, IOneNoteItem parent) { - sectionGroupTitle = sectionGroupTitle.Trim(); - bool validTitle = OneNoteParser.IsSectionGroupNameValid(sectionGroupTitle); + newSectionGroupName = newSectionGroupName.Trim(); + bool validTitle = OneNoteApplication.IsSectionGroupNameValid(newSectionGroupName); return new Result { - Title = $"Create section group: \"{sectionGroupTitle}\"", + Title = $"Create section group: \"{newSectionGroupName}\"", SubTitle = validTitle - ? $"Path: {GetNicePath(parent, true)} > {sectionGroupTitle}" - : $"Section group names cannot contain: {string.Join(' ', OneNoteParser.InvalidSectionGroupChars)}", - IcoPath = Icons.NewSectionGroup, + ? $"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, sectionGroupTitle); + OneNoteApplication.CreateSectionGroup(notebook, newSectionGroupName, showOneNote); break; case OneNoteSectionGroup sectionGroup: - OneNoteApplication.CreateSectionGroup(sectionGroup, sectionGroupTitle); - break; - default: + OneNoteApplication.CreateSectionGroup(sectionGroup, newSectionGroupName, showOneNote); break; } - context.API.ChangeQuery(context.CurrentPluginMetadata.ActionKeyword); - return true; - } + Main.ForceReQuery(); + if(showOneNote) + WindowHelper.FocusOneNote(); + + return showOneNote; + }, }; } - public Result CreateNewNotebookResult(string notebookTitle) + public Result CreateNewNotebookResult(string newNotebookName) { - notebookTitle = notebookTitle.Trim(); - bool validTitle = OneNoteParser.IsNotebookNameValid(notebookTitle); + newNotebookName = newNotebookName.Trim(); + bool validTitle = OneNoteApplication.IsNotebookNameValid(newNotebookName); return new Result { - Title = $"Create notebook: \"{notebookTitle}\"", + Title = $"Create notebook: \"{newNotebookName}\"", SubTitle = validTitle ? $"Location: {OneNoteApplication.GetDefaultNotebookLocation()}" - : $"Notebook names cannot contain: {string.Join(' ', OneNoteParser.InvalidNotebookChars)}", - IcoPath = Icons.NewNotebook, + : $"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(); - OneNoteApplication.CreateNotebook(notebookTitle); + return showOneNote; + }, + }; + } + + public List ContextMenu(Result selectedResult) + { + var results = new List(); + if (selectedResult.ContextData is IOneNoteItem item) + { + var result = CreateOneNoteItemResult(item, false); + result.Title = $"Open and sync \"{item.Name}\""; + result.SubTitle = string.Empty; + result.ContextData = null; + results.Add(result); - context.API.ChangeQuery(context.CurrentPluginMetadata.ActionKeyword); - return true; + if (item is not OneNotePage) + { + results.Add(new Result + { + Title = "Show in Notebook Explorer", + SubTitle = result.AutoCompleteText, + Score = - 1000, + IcoPath = iconProvider.NotebookExplorer, + Action = _ => + { + context.API.ChangeQuery(result.AutoCompleteText); + return false; + } + }); } - }; + } + return results; + } + + public List NoItemsInCollection(List results, IOneNoteItem parent) + { + // parent can be null if the collection only contains notebooks. + switch (parent) + { + case OneNoteNotebook: + case OneNoteSectionGroup: + // Can create section/section group + results.Add(NoItemsInCollectionResult("section", iconProvider.NewSection, "(unencrypted) section")); + results.Add(NoItemsInCollectionResult("section group", iconProvider.NewSectionGroup)); + break; + case OneNoteSection section: + // Can create page + if (!section.Locked) + { + results.Add(NoItemsInCollectionResult("page", iconProvider.NewPage, section: section)); + } + break; + } + + return results; + + Result NoItemsInCollectionResult(string title, string iconPath, string subTitle = null, OneNoteSection section = null) + { + return new Result + { + Title = $"Create {title}: \"\"", + SubTitle = $"No {subTitle ?? title}s found. Type a valid title to create one", + IcoPath = iconPath, + PreviewPanel = section != null ? GetNewPagePreviewPanel(section, null) : null , + }; + } } - #endregion + private Lazy GetNewPagePreviewPanel(OneNoteSection section, string pageTitle) => + new(() => new NewOneNotePagePreviewPanel(context, section, pageTitle)); - public static List NoMatchesFoundResult() + public static List NoMatchesFound() { return SingleResult("No matches found", "Try searching something else, or syncing your notebooks.", - Icons.Logo); + IconProvider.Logo); } - public static List InvalidQuery() + public List InvalidQuery() { return SingleResult("Invalid query", "The first character of the search must be a letter or a digit", - Icons.Warning); + iconProvider.Warning); + } + public List SearchingByTitle() + { + return SingleResult($"Now searching by title.", null, iconProvider.Search); } - public static List SingleResult(string title, string subTitle, string iconPath) + private static List SingleResult(string title, string subTitle, string iconPath) { return new List { @@ -267,5 +480,6 @@ public static List SingleResult(string title, string subTitle, string ic } }; } + } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/SearchManager.cs b/Flow.Launcher.Plugin.OneNote/SearchManager.cs index 1862b25..21ed884 100644 --- a/Flow.Launcher.Plugin.OneNote/SearchManager.cs +++ b/Flow.Launcher.Plugin.OneNote/SearchManager.cs @@ -9,339 +9,82 @@ public class SearchManager { private readonly PluginInitContext context; private readonly Settings settings; - private readonly ResultCreator rc; + private readonly ResultCreator resultCreator; + private readonly NotebookExplorer notebookExplorer; public SearchManager(PluginInitContext context, Settings settings, ResultCreator resultCreator) { this.context = context; this.settings = settings; - rc = resultCreator; + this.resultCreator = resultCreator; + notebookExplorer = new NotebookExplorer(this, resultCreator); } - #region Notebook Explorer - public List NotebookExplorer(Query query) - { - var results = new List(); - - string fullSearch = query.Search.Remove(query.Search.IndexOf(settings.Keywords.NotebookExplorer), settings.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 == searches[i]); - if (parent == null) - return results; - - collection = parent.Children; - } - string lastSearch = searches[^1]; - - results = lastSearch switch + internal List Query(Query query) + { + return query.Search switch { - //Empty search so show all in collection - string ls when string.IsNullOrWhiteSpace(ls) - => NotebookEmptySearch(parent, collection), + string s when s.StartsWith(settings.Keywords.RecentPages) + => RecentPages(s), - //Search by title - string ls when ls.StartsWith(settings.Keywords.TitleSearch) && parent is not OneNotePage - => TitleSearch(ls, collection, parent), + string s when s.StartsWith(settings.Keywords.NotebookExplorer) + => notebookExplorer.Query(query), - //scoped search - string ls when ls.StartsWith(settings.Keywords.ScopedSearch) && (parent is OneNoteNotebook || parent is OneNoteSectionGroup) - => ScopedSearch(ls, parent), + string s when s.StartsWith(settings.Keywords.TitleSearch) + => TitleSearch(s, null, OneNoteApplication.GetNotebooks()), - //default search - _ => NotebookDefaultSearch(parent, collection, lastSearch) + _ => DefaultSearch(query.Search), }; - - if (parent != null) - { - var result = rc.CreateOneNoteItemResult(parent, false, score: 4000); - result.Title = $"Open \"{parent.Name}\" in OneNote"; - result.SubTitle = lastSearch switch - { - string ls when ls.StartsWith(settings.Keywords.TitleSearch) - => $"Now search by title in \"{parent.Name}\"", - - string ls when ls.StartsWith(settings.Keywords.ScopedSearch) - => $"Now searching all pages in \"{parent.Name}\"", - - _ => $"Use \'{settings.Keywords.ScopedSearch}\' to search this item. Use \'{settings.Keywords.TitleSearch}\' to search by title in this item", - }; - - results.Add(result); - } - - return results; - } - - private List NotebookDefaultSearch(IOneNoteItem parent, IEnumerable collection, string lastSearch) - { - List highlightData = null; - int score = 0; - - var results = collection.Where(SettingsCheck) - .Where(item => FuzzySearch(item.Name, lastSearch, out highlightData, out score)) - .Select(item => rc.CreateOneNoteItemResult(item, true, highlightData, score)) - .ToList(); - - AddCreateNewOneNoteItemResults(results, parent, lastSearch); - return results; } - private List NotebookEmptySearch(IOneNoteItem parent, IEnumerable collection) + private List DefaultSearch(string query) { - List results = collection.Where(SettingsCheck) - .Select(item => rc.CreateOneNoteItemResult(item, true)) - .ToList(); - if (!results.Any()) - { - switch (parent) //parent can be null if the collection contains notebooks. - { - case OneNoteNotebook: - case OneNoteSectionGroup: - //can create section/section group - results.Add(NoItemsInCollectionResult("section", Icons.NewSection, "(unencrypted) section")); - results.Add(NoItemsInCollectionResult("section group", Icons.NewSectionGroup)); - break; - case OneNoteSection section: - //can create page - if (!section.Locked) - results.Add(NoItemsInCollectionResult("page", Icons.NewPage)); - break; - default: - break; - } - } - - return results; - - static Result NoItemsInCollectionResult(string title, string iconPath, string subTitle = null) + // Check for invalid start of query i.e. symbols + if (!char.IsLetterOrDigit(query[0])) { - return new Result - { - Title = $"Create {title}: \"\"", - SubTitle = $"No {subTitle ?? title}s found. Type a valid title to create one", - IcoPath = iconPath, - }; + return resultCreator.InvalidQuery(); } - } - - private List ScopedSearch(string query, IOneNoteItem parent) - { - if (query.Length == settings.Keywords.ScopedSearch.Length) - return ResultCreator.NoMatchesFoundResult(); - if (!char.IsLetterOrDigit(query[settings.Keywords.ScopedSearch.Length])) - return ResultCreator.InvalidQuery(); - - string currentSearch = query[settings.Keywords.TitleSearch.Length..]; - var results = new List(); - - results = OneNoteApplication.FindPages(parent, currentSearch) - .Select(pg => rc.CreatePageResult(pg, currentSearch)) - .ToList(); - - if (!results.Any()) - results = ResultCreator.NoMatchesFoundResult(); + var results = OneNoteApplication.FindPages(query) + .Select(pg => resultCreator.CreatePageResult(pg, query)); - return results; + return results.Any() ? results.ToList() : ResultCreator.NoMatchesFound(); } - private void AddCreateNewOneNoteItemResults(List results, IOneNoteItem parent, string query) + private List TitleSearch(string query, IOneNoteItem parent, IEnumerable currentCollection) { - if (!results.Any(result => string.Equals(query.Trim(), result.Title, StringComparison.OrdinalIgnoreCase))) + if (query.Length == settings.Keywords.TitleSearch.Length && parent == null) { - if (parent?.IsInRecycleBin() == true) - return; - - switch (parent) - { - case null: - results.Add(rc.CreateNewNotebookResult(query)); - break; - case OneNoteNotebook: - case OneNoteSectionGroup: - results.Add(rc.CreateNewSectionResult(query, parent)); - results.Add(rc.CreateNewSectionGroupResult(query, parent)); - break; - case OneNoteSection section: - if (!section.Locked) - results.Add(ResultCreator.CreateNewPageResult(query, section)); - break; - default: - break; - } + return resultCreator.SearchingByTitle(); } - } - #endregion - - public List EmptyQuery() - { - return new List - { - new Result - { - Title = "Search OneNote pages", - SubTitle = $"Type \"{settings.Keywords.NotebookExplorer}\" or select this option to search by notebook structure ", - AutoCompleteText = $"{context.CurrentPluginMetadata.ActionKeyword} {settings.Keywords.NotebookExplorer}", - IcoPath = Icons.Logo, - Score = 2000, - Action = c => - { - context.API.ChangeQuery($"{context.CurrentPluginMetadata.ActionKeyword} {settings.Keywords.NotebookExplorer}"); - return false; - }, - }, - new Result - { - Title = "See recent pages", - SubTitle = $"Type \"{settings.Keywords.RecentPages}\" or select this option to see recently modified pages", - AutoCompleteText = $"{context.CurrentPluginMetadata.ActionKeyword} {settings.Keywords.RecentPages}", - IcoPath = Icons.Recent, - Score = -1000, - Action = c => - { - context.API.ChangeQuery($"{context.CurrentPluginMetadata.ActionKeyword} {settings.Keywords.RecentPages}"); - return false; - }, - }, - new Result - { - Title = "New quick note", - IcoPath = Icons.NewPage, - Score = -4000, - Action = c => - { - OneNoteApplication.CreateQuickNote(); - return true; - } - }, - new Result - { - Title = "Open and sync notebooks", - IcoPath = Icons.Sync, - Score = int.MinValue, - Action = c => - { - foreach (var notebook in OneNoteApplication.GetNotebooks()) - { - notebook.Sync(); - } - OneNoteApplication.GetNotebooks() - .GetPages() - .OrderByDescending(pg => pg.LastModified) - .First() - .OpenInOneNote(); - return true; - } - }, - }; - } - public 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 => rc.CreatePageResult(pg, query)); - if (results.Any()) - return results.ToList(); - - return ResultCreator.NoMatchesFoundResult(); - } - public List TitleSearch(string query, IEnumerable currentCollection, IOneNoteItem parent = null) - { - if (query.Length == settings.Keywords.TitleSearch.Length && parent == null) - return ResultCreator.SingleResult($"Now searching by title.", null, Icons.Search); List highlightData = null; int score = 0; var currentSearch = query[settings.Keywords.TitleSearch.Length..]; - var results = currentCollection.Traverse(item => - { - if (!SettingsCheck(item)) - return false; - - return FuzzySearch(item.Name, currentSearch, out highlightData, out score); - }) - .Select(item => rc.CreateOneNoteItemResult(item, false, highlightData, score)) - .ToList(); + var results = currentCollection.Traverse(item => SettingsCheck(item) && FuzzySearch(item.Name, currentSearch, out highlightData, out score)) + .Select(item => resultCreator.CreateOneNoteItemResult(item, false, highlightData, score)) + .ToList(); - if (!results.Any()) - results = ResultCreator.NoMatchesFoundResult(); - - return results; + return results.Any() ? results : ResultCreator.NoMatchesFound(); } - public List RecentPages(string query) + + 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(pg => - { - Result result = rc.CreatePageResult(pg); - result.SubTitleToolTip = result.SubTitle; - result.SubTitle = $"{GetLastEdited(DateTime.Now - pg.LastModified)}\t{result.SubTitle}"; - result.IcoPath = Icons.RecentPage; - return result; - }) + .Select(resultCreator.CreateRecentPageResult) .ToList(); } - public List ContextMenu(Result selectedResult) - { - var results = new List(); - if (selectedResult.ContextData is IOneNoteItem item) - { - var result = rc.CreateOneNoteItemResult(item, false); - result.Title = $"Open and sync \"{item.Name}\""; - result.SubTitle = string.Empty; - result.ContextData = null; - results.Add(result); - } - return results; - } - private static string GetLastEdited(TimeSpan diff) - { - string lastEdited = "Last edited "; - if (PluralCheck(diff.TotalDays, "day", ref lastEdited) - || PluralCheck(diff.TotalHours, "hour", ref lastEdited) - || PluralCheck(diff.TotalMinutes, "min", ref lastEdited) - || PluralCheck(diff.TotalSeconds, "sec", ref lastEdited)) - return lastEdited; - else - return lastEdited += "Now."; - - static bool PluralCheck(double totalTime, string timeType, ref string lastEdited) - { - var roundedTime = (int)Math.Round(totalTime); - if (roundedTime > 0) - { - string plural = roundedTime == 1 ? "" : "s"; - lastEdited += $"{roundedTime} {timeType}{plural} ago."; - return true; - } - else - return false; - - } - } private bool FuzzySearch(string itemName, string search, out List highlightData, out int score) { var matchResult = context.API.FuzzySearch(search, itemName); @@ -359,7 +102,166 @@ private bool SettingsCheck(IOneNoteItem item) success = false; return success; } + 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 search 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 = new List(); + + 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))) + { + 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/Settings.cs b/Flow.Launcher.Plugin.OneNote/Settings.cs index ba823fc..86a4876 100644 --- a/Flow.Launcher.Plugin.OneNote/Settings.cs +++ b/Flow.Launcher.Plugin.OneNote/Settings.cs @@ -1,4 +1,6 @@ -namespace Flow.Launcher.Plugin.OneNote +using Flow.Launcher.Plugin.OneNote.Icons; + +namespace Flow.Launcher.Plugin.OneNote { public class Settings : UI.Model { @@ -7,6 +9,7 @@ public class Settings : UI.Model private bool showRecycleBin = true; private bool showEncrypted = false; private bool createColoredIcons = true; + private IconTheme iconTheme = IconTheme.Color; public Keywords Keywords { get; init; } = new Keywords(); public bool ShowRecycleBin @@ -34,5 +37,11 @@ public bool CreateColoredIcons get => createColoredIcons; set => SetProperty(ref createColoredIcons, value); } + + public IconTheme IconTheme + { + get => iconTheme; + set => SetProperty(ref iconTheme, value); + } } } diff --git a/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs b/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs new file mode 100644 index 0000000..e462b62 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/UI/RelayCommand.cs @@ -0,0 +1,34 @@ +#nullable enable +using System; +using System.Windows.Input; + +namespace Flow.Launcher.Plugin.OneNote.UI +{ + 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) + { + return canExecute?.Invoke(parameter) != false; + } + + public void Execute(object? parameter) + { + execute(parameter); + } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs index e4f4ca8..9518866 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/ChangeKeywordViewModel.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Linq; +using System.Windows.Input; namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels { @@ -6,46 +8,64 @@ public class ChangeKeywordViewModel : Model { private readonly PluginInitContext context; private readonly KeywordViewModel[] keywords; - private string newKeyword; + private readonly Action closeAction; - public ChangeKeywordViewModel(SettingsViewModel settingsViewModel) + private string errorMessage; + + public ChangeKeywordViewModel(SettingsViewModel settingsViewModel, PluginInitContext context, Action close) { - context = settingsViewModel.context; + this.context = context; + closeAction = close; keywords = settingsViewModel.Keywords; SelectedKeyword = settingsViewModel.SelectedKeyword; + ChangeKeywordCommand = new RelayCommand( + keyword => ChangeKeyword((string)keyword), + keyword => CanChangeKeyword((string)keyword)); + CloseCommand = new RelayCommand( _=> closeAction?.Invoke()); + } + public KeywordViewModel SelectedKeyword { get; } + + public ICommand CloseCommand { get; } + + public ICommand ChangeKeywordCommand { get; } + + public string ErrorMessage + { + get => errorMessage; + private set => SetProperty(ref errorMessage, value); } - public KeywordViewModel SelectedKeyword { get; init; } - public string NewKeyword { get => newKeyword; set => SetProperty(ref newKeyword, value); } - public bool ChangeKeyword(out string errorMessage) + private bool CanChangeKeyword(string newKeyword) { - errorMessage = null; - var oldKeyword = SelectedKeyword.Keyword; - if (string.IsNullOrWhiteSpace(NewKeyword)) + if (string.IsNullOrWhiteSpace(newKeyword)) { - errorMessage = "The new keyword cannot be empty."; + //ErrorMessage = "The new keyword cannot be empty."; return false; } - var newKeyword = NewKeyword.Trim(); - if (oldKeyword == newKeyword) + newKeyword = newKeyword.Trim(); + if (SelectedKeyword.Keyword == newKeyword) { - errorMessage = "The new keyword is the same as the old keyword."; + ErrorMessage = "The new keyword is the same as the old keyword."; return false; } var alreadySetKeyword = keywords.FirstOrDefault(k => k.Keyword == newKeyword); if (alreadySetKeyword != null) { - errorMessage = $"The new keyword matches an already set one:\n" - + $"\"{alreadySetKeyword.Name}\" => \"{alreadySetKeyword.Keyword}\""; + ErrorMessage = $"The new keyword is already set for {alreadySetKeyword.Name}."; return false; } - SelectedKeyword.Keyword = newKeyword; - context.API.SaveSettingJsonStorage(); + ErrorMessage = null; return true; + } + private void ChangeKeyword(string newKeyword) + { + SelectedKeyword.Keyword = newKeyword.Trim(); + context.API.SaveSettingJsonStorage(); + closeAction?.Invoke(); } } diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/IconThemeViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/IconThemeViewModel.cs new file mode 100644 index 0000000..f53bd89 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/IconThemeViewModel.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; +using Flow.Launcher.Plugin.OneNote.Icons; + +namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels +{ + public class IconThemeViewModel + { + private IconThemeViewModel(IconTheme iconTheme, PluginInitContext context) + { + IconTheme = iconTheme; + if (iconTheme == IconTheme.System) + { + Name = "FL Default"; + ImageUri = GetUri(IconTheme.Light.ToString(), context); + ImageUri2 = GetUri(IconTheme.Dark.ToString(), context); + Tooltip = "Matches Flow Launcher's app theme"; + } + else + { + Name = Enum.GetName(iconTheme); + ImageUri = GetUri(Name, context); + } + } + + private static Uri GetUri(string theme, PluginInitContext context) => + new($"{context.CurrentPluginMetadata.PluginDirectory}/{IconConstants.ImagesDirectory}{IconConstants.Notebook}.{theme.ToLower()}.png"); + + public string Name { get; } + public IconTheme IconTheme { get; } + public Uri ImageUri { get; } + public Uri ImageUri2 { get; } + public string Tooltip { get; } + + public static IconThemeViewModel[] GetIconThemeViewModels(PluginInitContext context) => + Enum.GetValues().Select(iconTheme => new IconThemeViewModel(iconTheme, context)).ToArray(); + } + +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs index b87bd9d..9cf4f98 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/KeywordViewModel.cs @@ -1,14 +1,14 @@ using System.Reflection; using System.Linq; -using System.Text.RegularExpressions; +using Humanizer; namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels { - public partial class KeywordViewModel : BaseModel + public class KeywordViewModel : BaseModel { - public object Instance { get; init; } - public PropertyInfo PropertyInfo { get; init; } - public string Name { get; init; } + private object Instance { get; init; } + private PropertyInfo PropertyInfo { get; init; } + public string Name { get; private init; } public string Keyword { @@ -16,11 +16,10 @@ public string Keyword set { PropertyInfo.SetValue(Instance, value, null); - OnPropertyChanged(nameof(Keyword)); + OnPropertyChanged(); } } - - + public static KeywordViewModel[] GetKeywordViewModels(Keywords keywords) { return keywords.GetType() @@ -29,12 +28,9 @@ public static KeywordViewModel[] GetKeywordViewModels(Keywords keywords) { Instance = keywords, PropertyInfo = p, - Name = NicfyPropertyName().Replace(p.Name, " $1"), + Name = p.Name.Humanize(LetterCasing.Title) }) .ToArray(); } - - [GeneratedRegex("(\\B[A-Z])")] - private static partial Regex NicfyPropertyName(); } } diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs new file mode 100644 index 0000000..651bcb0 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/NewOneNotePageViewModel.cs @@ -0,0 +1,61 @@ +using System; +using System.Windows.Input; +using Flow.Launcher.Plugin.OneNote.Icons; +using Odotocodot.OneNote.Linq; + +namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels +{ + public class NewOneNotePageViewModel : Model + { + private string pageTitle; + private string pageContent; + private readonly OneNoteSection section; + private readonly PluginInitContext context; + + public NewOneNotePageViewModel(PluginInitContext context, OneNoteSection section, string pageTitle) + { + this.context = context; + this.section = section; + PageTitle = pageTitle; + CreateCommand = new RelayCommand(_ => CreatePage(false)); + CreateAndOpenCommand = new RelayCommand(_ => CreatePage(true)); + } + + private void CreatePage(bool openImmediately) + { + var id = OneNoteApplication.CreatePage(section, PageTitle, false); + var page = (OneNotePage)OneNoteItemExtensions.FindByID(id); + var pageContentXml = page.GetPageContent(); + var xmlWrap = $""; + pageContentXml = pageContentXml.Insert(pageContentXml.IndexOf("", StringComparison.Ordinal), xmlWrap); + OneNoteApplication.UpdatePageContent(pageContentXml); + Main.ForceReQuery(); + if (openImmediately) + { + page.OpenInOneNote(); + context.API.HideMainWindow(); + WindowHelper.FocusOneNote(); + } + else + { + context.API.ShowMsg("Page Created in OneNote", + $"Title: {PageTitle}", + $"{context.CurrentPluginMetadata.PluginDirectory}/{IconProvider.Logo}"); + } + } + public string PageTitle + { + get => pageTitle; + set => SetProperty(ref pageTitle, value); + } + + public string PageContent + { + get => pageContent; + set => SetProperty(ref pageContent, value); + } + + public ICommand CreateCommand { get; } + public ICommand CreateAndOpenCommand { get; } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs index 4a0e9b3..5200407 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/ViewModels/SettingsViewModel.cs @@ -1,34 +1,89 @@ - -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Input; +using Flow.Launcher.Plugin.OneNote.Icons; +using Humanizer; +using Modern = ModernWpf.Controls; namespace Flow.Launcher.Plugin.OneNote.UI.ViewModels { public class SettingsViewModel : Model { - public readonly PluginInitContext context; - public SettingsViewModel(PluginInitContext context, Settings settings) + private readonly IconProvider iconProvider; + private KeywordViewModel selectedKeyword; + + public SettingsViewModel(PluginInitContext context, Settings settings, IconProvider iconProvider) { + this.iconProvider = iconProvider; Settings = settings; - this.context = context; - Keywords = KeywordViewModel.GetKeywordViewModels(settings.Keywords); - Icons = Icons.Instance; + Keywords = KeywordViewModel.GetKeywordViewModels(settings.Keywords); + IconThemes = IconThemeViewModel.GetIconThemeViewModels(context); + + EditCommand = new RelayCommand( + _ => new Views.ChangeKeywordWindow(this, context).ShowDialog(), //Avert your eyes! This is not MVVM! + _ => SelectedKeyword != null); + + OpenGeneratedIconsFolderCommand = new RelayCommand( + _ => context.API.OpenDirectory(iconProvider.GeneratedImagesDirectoryInfo.FullName)); + + ClearCachedIconsCommand = new RelayCommand( + async _ => await ClearCachedIcons(), + _ => iconProvider.CachedIconCount > 0); + + iconProvider.PropertyChanged += (_, args) => + { + if (args.PropertyName == nameof(iconProvider.CachedIconCount)) + { + OnPropertyChanged(nameof(CachedIconsFileSize)); + CommandManager.InvalidateRequerySuggested(); + } + }; + settings.PropertyChanged += (_, args) => + { + if (args.PropertyName == nameof(Settings.IconTheme)) + { + Main.ForceReQuery(); + } + }; + SelectedKeyword = Keywords[0]; } + public ICommand EditCommand { get; } + public ICommand OpenGeneratedIconsFolderCommand { get; } + public ICommand ClearCachedIconsCommand { get; } + public Settings Settings { get; } + public KeywordViewModel[] Keywords { get; } + public IconThemeViewModel[] IconThemes { get; } + public string CachedIconsFileSize => iconProvider.GeneratedImagesDirectoryInfo.EnumerateFiles() + .Select(file => file.Length) + .Aggregate(0L, (a, b) => a + b) + .Bytes() + .Humanize(); - public Settings Settings { get; init; } - public KeywordViewModel[] Keywords { get; init; } - public Icons Icons { get; init; } - public KeywordViewModel SelectedKeyword { get; set; } + public KeywordViewModel SelectedKeyword + { + get => selectedKeyword; + set => SetProperty(ref selectedKeyword, value); + } + + //quick and dirty non MVVM stuffs + private async Task ClearCachedIcons() + { + var dialog = new Modern.ContentDialog() + { + Title = "Clear Cached Icons", + Content = $"Delete cached notebook and sections icons.\n" + + $"This will delete {"icon".ToQuantity(iconProvider.CachedIconCount)}.", + PrimaryButtonText = "Yes", + CloseButtonText = "Cancel", + DefaultButton = Modern.ContentDialogButton.Close, + }; -#pragma warning disable CA1822 // Mark members as static - public IEnumerable DefaultRecentCountOptions => Enumerable.Range(1, 16); -#pragma warning restore CA1822 // Mark members as static + var result = await dialog.ShowAsync(); - public string NotebookIcon => Path.Combine(context.CurrentPluginMetadata.PluginDirectory, Icons.Notebook); - public string SectionIcon => Path.Combine(context.CurrentPluginMetadata.PluginDirectory, Icons.Section); - public void OpenNotebookIconsFolder() => context.API.OpenDirectory(Icons.NotebookIconDirectory); - public void OpenSectionIconsFolder() => context.API.OpenDirectory(Icons.SectionIconDirectory); - public void ClearCachedIcons() => Icons.ClearCachedIcons(); + if (result == Modern.ContentDialogResult.Primary) + { + iconProvider.ClearCachedIcons(); + } + } } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml b/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml index 0b3ff0a..8c172f1 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml +++ b/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml @@ -10,7 +10,7 @@ d:DataContext="{d:DesignInstance Type=vm:ChangeKeywordViewModel}" Background="{DynamicResource PopuBGColor}" Foreground="{DynamicResource PopupTextColor}" - Loaded="WindowLoaded" + FocusManager.FocusedElement="{Binding ElementName=TextBox_NewKeyword}" ResizeMode="NoResize" SizeToContent="Height" WindowStartupLocation="CenterScreen" @@ -60,7 +60,8 @@ diff --git a/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml.cs b/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml.cs index 965dce3..88054a6 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml.cs +++ b/Flow.Launcher.Plugin.OneNote/UI/Views/ChangeKeywordWindow.xaml.cs @@ -5,34 +5,10 @@ namespace Flow.Launcher.Plugin.OneNote.UI.Views { public partial class ChangeKeywordWindow { - private readonly ChangeKeywordViewModel viewModel; - - public ChangeKeywordWindow(SettingsViewModel settingsViewModel) + public ChangeKeywordWindow(SettingsViewModel viewModel, PluginInitContext context) { InitializeComponent(); - DataContext = viewModel = new ChangeKeywordViewModel(settingsViewModel); - } - - private void WindowLoaded(object sender, RoutedEventArgs e) - { - TextBox_NewKeyword.Focus(); - } - - private void CloseWindow(object sender, RoutedEventArgs e) - { - Close(); - } - - private void Button_ChangeKeyword(object sender, RoutedEventArgs e) - { - if(viewModel.ChangeKeyword(out string errorMessage)) - { - Close(); - } - else - { - MessageBox.Show(this, errorMessage,"Invalid Keyword", MessageBoxButton.OK, MessageBoxImage.Error); - } + DataContext = new ChangeKeywordViewModel(viewModel, context, Close); } } } diff --git a/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml b/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml new file mode 100644 index 0000000..a085a4e --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml.cs b/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml.cs new file mode 100644 index 0000000..7a66ea2 --- /dev/null +++ b/Flow.Launcher.Plugin.OneNote/UI/Views/NewOneNotePagePreviewPanel.xaml.cs @@ -0,0 +1,36 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using Flow.Launcher.Plugin.OneNote.UI.ViewModels; +using Odotocodot.OneNote.Linq; + +namespace Flow.Launcher.Plugin.OneNote.UI.Views +{ + public partial class NewOneNotePagePreviewPanel : UserControl + { + public NewOneNotePagePreviewPanel(PluginInitContext context, OneNoteSection section, string pageTitle) + { + DataContext = new NewOneNotePageViewModel(context, section, pageTitle); + InitializeComponent(); + } + private void NewOneNotePagePreviewPanel_OnLoaded(object sender, RoutedEventArgs e) + { + FocusTextBox(); + } + + private void NewOneNotePagePreviewPanel_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if((bool)e.NewValue) + { + FocusTextBox(); + } + } + + private void FocusTextBox() + { + TextBoxPageTitle.Focus(); + Keyboard.Focus(TextBoxPageTitle); + TextBoxPageTitle.CaretIndex = TextBoxPageTitle.Text.Length; + } + } +} \ No newline at end of file diff --git a/Flow.Launcher.Plugin.OneNote/UI/Views/SettingsView.xaml b/Flow.Launcher.Plugin.OneNote/UI/Views/SettingsView.xaml index fdbb7af..31e1470 100644 --- a/Flow.Launcher.Plugin.OneNote/UI/Views/SettingsView.xaml +++ b/Flow.Launcher.Plugin.OneNote/UI/Views/SettingsView.xaml @@ -3,7 +3,9 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:linq="clr-namespace:System.Linq;assembly=System.Core" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:ui="http://schemas.modernwpf.com/2019" xmlns:vm="clr-namespace:Flow.Launcher.Plugin.OneNote.UI.ViewModels" d:DataContext="{d:DesignInstance Type=vm:SettingsViewModel}" @@ -17,6 +19,15 @@ + + + 1 + 20 + + @@ -80,57 +91,13 @@ Margin="0,4,4,4" HorizontalAlignment="Center" HorizontalContentAlignment="Center" - Click="ClearCachedIcons"> + Command="{Binding ClearCachedIconsCommand}"> - - - - @@ -139,7 +106,60 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -148,10 +168,10 @@ @@ -183,7 +203,7 @@ HorizontalAlignment="Right" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" - Click="EditButton_Click"> + Command="{Binding EditCommand}">