Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2e1e21c
Update dependencies
Odotocodot Jul 13, 2025
9da1f5f
Use new API
Odotocodot Jul 20, 2025
eddd738
Refactor search manager
Odotocodot Jul 20, 2025
aafa81f
Refactor search manager (cont.)
Odotocodot Jul 20, 2025
9ee9ae8
Refactor keywords
Odotocodot Jul 26, 2025
88b678e
Refactor view models
Odotocodot Jul 26, 2025
41ad0a4
Update search classes with keyword
Odotocodot Jul 26, 2025
35995a1
Refactor ResultCreator
Odotocodot Jul 27, 2025
beaa2ff
Small tweaks and code cleanup
Odotocodot Jul 27, 2025
423621d
Update keyword changing logic
Odotocodot Aug 1, 2025
5429742
Update Flow.Launcher.Plugin to 5.0.0
Odotocodot Oct 4, 2025
8694387
migrate to `LinqToOneNote` [1/2]
Odotocodot Jan 3, 2026
f683c2e
migrate to `LinqToOneNote` [2/2]
Odotocodot Jan 8, 2026
59f3f2b
Style tweaks
Odotocodot Jan 8, 2026
d34a9a2
Improve notebook explorer performance
Odotocodot Jan 8, 2026
d96f4e1
Remove uneeded path caching
Odotocodot Feb 14, 2026
dab09f4
Small refactor
Odotocodot Mar 3, 2026
7b5be27
Add copy link to clipboard command
Odotocodot Mar 3, 2026
9cbc7b8
Add design time UI for settings page
Odotocodot Jan 11, 2026
3083ace
Add design time text to settings view
Odotocodot Jan 11, 2026
9eff270
Migrate to `iNKORE.UI.WPF.Modern` UI Framework
Odotocodot Jan 11, 2026
fd34ffb
Add minimum app version
Odotocodot Mar 4, 2026
dde0ec7
Fix grammar
Odotocodot Mar 4, 2026
840e72b
Fix crash on new quick note
Odotocodot Mar 4, 2026
bf5407f
Update Changelog
Odotocodot Mar 4, 2026
eb081de
Update Readme
Odotocodot Mar 4, 2026
6c6137f
Bump plugin version to 3.0.0
Odotocodot Mar 4, 2026
28e6171
Fix inverted settings
Odotocodot Mar 4, 2026
21b1ce6
Update publish workflow
Odotocodot Mar 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,4 @@ MigrationBackup/

#VSCode
.vscode/
.idea/
18 changes: 15 additions & 3 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -17,7 +29,7 @@

- Fixed spelling and grammar mistakes.

## 2.1.0 - 2024-6-24
## 2.1.0 - 2024-06-24

### Added

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net9.0-windows</TargetFramework>
<AssemblyName>Flow.Launcher.Plugin.OneNote</AssemblyName>
<PackageId>Flow.Launcher.Plugin.OneNote</PackageId>
<Authors>Odotocodot</Authors>
Expand All @@ -10,6 +10,7 @@
<PackageTags>flow-launcher flow-plugin</PackageTags>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<UseWPF>true</UseWPF>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <!-- Required for bringing OneNote to front -->
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
Expand All @@ -27,11 +28,11 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Flow.Launcher.Plugin" Version="4.1.0" />
<PackageReference Include="Flow.Launcher.Plugin" Version="5.2.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="ModernWpfUI" Version="0.9.6" />
<PackageReference Include="Odotocodot.OneNote.Linq" Version="1.1.0" />
<PackageReference Include="LinqToOneNote" Version="2.1.0" />
<PackageReference Include="System.Drawing.Common" Version="8.0.6" />
<PackageReference Include="iNKORE.UI.WPF.Modern" Version="0.10.1" />
</ItemGroup>

<ItemGroup>
Expand Down
42 changes: 22 additions & 20 deletions Flow.Launcher.Plugin.OneNote/Icons/IconGeneratorInfo.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
45 changes: 23 additions & 22 deletions Flow.Launcher.Plugin.OneNote/Icons/IconProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, ImageSource> iconCache = new();

public const string Logo = IC.ImagesDirectory + IC.Logo + ".png";
public string Sync => GetIconPath(IC.Sync);
public string Search => GetIconPath(IC.Search);
Expand All @@ -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<string,ImageSource> 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)
{
Expand All @@ -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()
Expand All @@ -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();
Expand Down
40 changes: 35 additions & 5 deletions Flow.Launcher.Plugin.OneNote/Keywords.cs
Original file line number Diff line number Diff line change
@@ -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<Keyword>
{
public override Keyword Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> new(JsonSerializer.Deserialize<string>(ref reader, options)!);

public override void Write(Utf8JsonWriter writer, Keyword value, JsonSerializerOptions options)
=> JsonSerializer.Serialize(writer, value.Value, options);
}

}
56 changes: 31 additions & 25 deletions Flow.Launcher.Plugin.OneNote/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Settings>();

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<List<Result>> QueryAsync(Query query, CancellationToken token)
{
currentQuery = query;
var init = OneNoteInitAsync(token);
Task init = OneNoteInitAsync(token);

if (string.IsNullOrEmpty(query.Search))
return resultCreator.EmptyQuery();
Expand All @@ -64,8 +72,6 @@ public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
return searchManager.Query(query);
}

public static void ForceReQuery() => instance.context.API.ChangeQuery(instance.currentQuery.RawQuery, true);

public List<Result> LoadContextMenus(Result selectedResult)
{
return resultCreator.ContextMenu(selectedResult);
Expand All @@ -78,9 +84,9 @@ public Control CreateSettingPanel()

public void Dispose()
{
context.API.VisibilityChanged -= OnVisibilityChanged;
visibilityChanged.Dispose();
semaphore.Dispose();
OneNoteApplication.ReleaseComObject();
OneNoteApp.ReleaseComObject();
}
}
}
Loading