diff --git a/Flow.Launcher.sln b/Flow.Launcher.sln index e44b23232fb..117e430a53f 100644 --- a/Flow.Launcher.sln +++ b/Flow.Launcher.sln @@ -53,14 +53,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution LICENSE = LICENSE Scripts\post_build.ps1 = Scripts\post_build.ps1 README.md = README.md - SolutionAssemblyInfo.cs = SolutionAssemblyInfo.cs Settings.XamlStyler = Settings.XamlStyler + SolutionAssemblyInfo.cs = SolutionAssemblyInfo.cs EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Shell", "Plugins\Flow.Launcher.Plugin.Shell\Flow.Launcher.Plugin.Shell.csproj", "{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.BrowserBookmark", "Plugins\Flow.Launcher.Plugin.BrowserBookmark\Flow.Launcher.Plugin.BrowserBookmark.csproj", "{9B130CC5-14FB-41FF-B310-0A95B6894C37}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Calculator", "Plugins\Flow.Launcher.Plugin.Calculator\Flow.Launcher.Plugin.Calculator.csproj", "{59BD9891-3837-438A-958D-ADC7F91F6F7E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Explorer", "Plugins\Flow.Launcher.Plugin.Explorer\Flow.Launcher.Plugin.Explorer.csproj", "{F9C4C081-4CC3-4146-95F1-E102B4E10A5F}" @@ -71,6 +69,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Plugin EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.WindowsSettings", "Plugins\Flow.Launcher.Plugin.WindowsSettings\Flow.Launcher.Plugin.WindowsSettings.csproj", "{5043CECE-E6A7-4867-9CBE-02D27D83747A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flow.Launcher.Plugin.BrowserBookmarks", "Plugins\Flow.Launcher.Plugin.BrowserBookmarks\Flow.Launcher.Plugin.BrowserBookmarks.csproj", "{9B130CC5-14FB-41FF-B310-0A95B6894C37}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -81,8 +81,19 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.ActiveCfg = Debug|Any CPU + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.Build.0 = Debug|Any CPU + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.ActiveCfg = Debug|Any CPU + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.Build.0 = Debug|Any CPU + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.Build.0 = Release|Any CPU + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.ActiveCfg = Release|Any CPU + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.Build.0 = Release|Any CPU + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.ActiveCfg = Release|Any CPU + {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.Build.0 = Release|Any CPU {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|Any CPU.Build.0 = Debug|Any CPU {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.ActiveCfg = Debug|Any CPU {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x64.Build.0 = Debug|Any CPU {FF742965-9A80-41A5-B042-D6C7D3A21708}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -105,18 +116,6 @@ Global {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x64.Build.0 = Release|Any CPU {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x86.ActiveCfg = Release|Any CPU {8451ECDD-2EA4-4966-BB0A-7BBC40138E80}.Release|x86.Build.0 = Release|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.ActiveCfg = Debug|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x64.Build.0 = Debug|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.ActiveCfg = Debug|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Debug|x86.Build.0 = Debug|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|Any CPU.Build.0 = Release|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.ActiveCfg = Release|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x64.Build.0 = Release|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.ActiveCfg = Release|Any CPU - {DB90F671-D861-46BB-93A3-F1304F5BA1C5}.Release|x86.Build.0 = Release|Any CPU {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|Any CPU.Build.0 = Debug|Any CPU {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -214,18 +213,6 @@ Global {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|x64.Build.0 = Release|Any CPU {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|x86.ActiveCfg = Release|Any CPU {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}.Release|x86.Build.0 = Release|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|x64.ActiveCfg = Debug|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|x64.Build.0 = Debug|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|x86.ActiveCfg = Debug|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|x86.Build.0 = Debug|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|Any CPU.Build.0 = Release|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|x64.ActiveCfg = Release|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|x64.Build.0 = Release|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|x86.ActiveCfg = Release|Any CPU - {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|x86.Build.0 = Release|Any CPU {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Debug|Any CPU.Build.0 = Debug|Any CPU {59BD9891-3837-438A-958D-ADC7F91F6F7E}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -286,6 +273,18 @@ Global {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x64.Build.0 = Release|Any CPU {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x86.ActiveCfg = Release|Any CPU {5043CECE-E6A7-4867-9CBE-02D27D83747A}.Release|x86.Build.0 = Release|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|x64.ActiveCfg = Debug|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|x64.Build.0 = Debug|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|x86.ActiveCfg = Debug|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Debug|x86.Build.0 = Debug|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|Any CPU.Build.0 = Release|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|x64.ActiveCfg = Release|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|x64.Build.0 = Release|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|x86.ActiveCfg = Release|Any CPU + {9B130CC5-14FB-41FF-B310-0A95B6894C37}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -297,12 +296,12 @@ Global {0B9DE348-9361-4940-ADB6-F5953BFFCCEC} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {A3DCCBCA-ACC1-421D-B16E-210896234C26} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} - {9B130CC5-14FB-41FF-B310-0A95B6894C37} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {F9C4C081-4CC3-4146-95F1-E102B4E10A5F} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {588088F4-3262-4F9F-9663-A05DE12534C3} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {4792A74A-0CEA-4173-A8B2-30E6764C6217} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} {5043CECE-E6A7-4867-9CBE-02D27D83747A} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} + {9B130CC5-14FB-41FF-B310-0A95B6894C37} = {3A73F5A7-0335-40D8-BF7C-F20BE5D0BA87} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {F26ACB50-3F6C-4907-B0C9-1ADACC1D0DED} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromeBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromeBookmarkLoader.cs deleted file mode 100644 index 65757b80253..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromeBookmarkLoader.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Flow.Launcher.Plugin.BrowserBookmark.Models; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Flow.Launcher.Plugin.BrowserBookmark; - -public class ChromeBookmarkLoader : ChromiumBookmarkLoader -{ - public override List GetBookmarks() - { - return LoadChromeBookmarks(); - } - - private List LoadChromeBookmarks() - { - var bookmarks = new List(); - var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium")); - return bookmarks; - } -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs deleted file mode 100644 index b1166146670..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Text.Json; -using System.Threading.Tasks; -using Flow.Launcher.Plugin.BrowserBookmark.Helper; -using Flow.Launcher.Plugin.BrowserBookmark.Models; -using Microsoft.Data.Sqlite; - -namespace Flow.Launcher.Plugin.BrowserBookmark; - -public abstract class ChromiumBookmarkLoader : IBookmarkLoader -{ - private static readonly string ClassName = nameof(ChromiumBookmarkLoader); - - private readonly string _faviconCacheDir; - - protected ChromiumBookmarkLoader() - { - _faviconCacheDir = Main._faviconCacheDir; - } - - public abstract List GetBookmarks(); - - protected List LoadBookmarks(string browserDataPath, string name) - { - var bookmarks = new List(); - if (!Directory.Exists(browserDataPath)) return bookmarks; - var paths = Directory.GetDirectories(browserDataPath); - - foreach (var profile in paths) - { - var bookmarkPath = Path.Combine(profile, "Bookmarks"); - if (!File.Exists(bookmarkPath)) - continue; - - // Register bookmark file monitoring (direct call to Main.RegisterBookmarkFile) - try - { - if (File.Exists(bookmarkPath)) - { - Main.RegisterBookmarkFile(bookmarkPath); - } - } - catch (Exception ex) - { - Main.Context.API.LogException(ClassName, $"Failed to register bookmark file monitoring: {bookmarkPath}", ex); - continue; - } - - var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})"); - var profileBookmarks = LoadBookmarksFromFile(bookmarkPath, source); - - // Load favicons after loading bookmarks - if (Main._settings.EnableFavicons) - { - var faviconDbPath = Path.Combine(profile, "Favicons"); - if (File.Exists(faviconDbPath)) - { - Main.Context.API.StopwatchLogInfo(ClassName, $"Load {profileBookmarks.Count} favicons cost", () => - { - LoadFaviconsFromDb(faviconDbPath, profileBookmarks); - }); - } - } - - bookmarks.AddRange(profileBookmarks); - } - - return bookmarks; - } - - protected static List LoadBookmarksFromFile(string path, string source) - { - var bookmarks = new List(); - - if (!File.Exists(path)) - return bookmarks; - - using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); - if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) - return bookmarks; - EnumerateRoot(rootElement, bookmarks, source); - return bookmarks; - } - - private static void EnumerateRoot(JsonElement rootElement, ICollection bookmarks, string source) - { - foreach (var folder in rootElement.EnumerateObject()) - { - if (folder.Value.ValueKind != JsonValueKind.Object) - continue; - - // Fix for Opera. It stores bookmarks slightly different than chrome. - if (folder.Name == "custom_root") - EnumerateRoot(folder.Value, bookmarks, source); - else - EnumerateFolderBookmark(folder.Value, bookmarks, source); - } - } - - private static void EnumerateFolderBookmark(JsonElement folderElement, ICollection bookmarks, - string source) - { - if (!folderElement.TryGetProperty("children", out var childrenElement)) - return; - foreach (var subElement in childrenElement.EnumerateArray()) - { - if (subElement.TryGetProperty("type", out var type)) - { - switch (type.GetString()) - { - case "folder": - case "workspace": // Edge Workspace - EnumerateFolderBookmark(subElement, bookmarks, source); - break; - default: - bookmarks.Add(new Bookmark( - subElement.GetProperty("name").GetString(), - subElement.GetProperty("url").GetString(), - source)); - break; - } - } - else - { - Main.Context.API.LogError(ClassName, $"type property not found for {subElement.GetString()}"); - } - } - } - - private void LoadFaviconsFromDb(string dbPath, List bookmarks) - { - FaviconHelper.LoadFaviconsFromDb(_faviconCacheDir, dbPath, (tempDbPath) => - { - // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates - var savedPaths = new ConcurrentDictionary(); - - // Get favicons based on bookmarks concurrently - Parallel.ForEach(bookmarks, bookmark => - { - // Use read-only connection to avoid locking issues - // Do not use pooling so that we do not need to clear pool: https://github.com/dotnet/efcore/issues/26580 - var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly;Pooling=false"); - connection.Open(); - - try - { - var url = bookmark.Url; - if (string.IsNullOrEmpty(url)) return; - - // Extract domain from URL - if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) - return; - - var domain = uri.Host; - - using var cmd = connection.CreateCommand(); - cmd.CommandText = @" - SELECT f.id, b.image_data - FROM favicons f - JOIN favicon_bitmaps b ON f.id = b.icon_id - JOIN icon_mapping m ON f.id = m.icon_id - WHERE m.page_url LIKE @url - ORDER BY b.width DESC - LIMIT 1"; - - cmd.Parameters.AddWithValue("@url", $"%{domain}%"); - - using var reader = cmd.ExecuteReader(); - if (!reader.Read() || reader.IsDBNull(1)) - return; - - var iconId = reader.GetInt64(0).ToString(); - var imageData = (byte[])reader["image_data"]; - - if (imageData is not { Length: > 0 }) - return; - - var faviconPath = Path.Combine(_faviconCacheDir, $"chromium_{domain}_{iconId}.png"); - - // Filter out duplicate favicons - if (savedPaths.TryAdd(faviconPath, true)) - { - FaviconHelper.SaveBitmapData(imageData, faviconPath); - } - - bookmark.FaviconPath = faviconPath; - } - catch (Exception ex) - { - Main.Context.API.LogException(ClassName, $"Failed to extract bookmark favicon: {bookmark.Url}", ex); - } - finally - { - // Cache connection and clear pool after all operations to avoid issue: - // ObjectDisposedException: Safe handle has been closed. - connection.Close(); - connection.Dispose(); - } - }); - }); - } -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs deleted file mode 100644 index b76adae93c3..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Flow.Launcher.Plugin.BrowserBookmark.Models; -using Flow.Launcher.Plugin.SharedModels; - -namespace Flow.Launcher.Plugin.BrowserBookmark.Commands; - -internal static class BookmarkLoader -{ - internal static MatchResult MatchProgram(Bookmark bookmark, string queryString) - { - var match = Main.Context.API.FuzzySearch(queryString, bookmark.Name); - if (match.IsSearchPrecisionScoreMet()) - return match; - - return Main.Context.API.FuzzySearch(queryString, bookmark.Url); - } - - internal static List LoadAllBookmarks(Settings setting) - { - var allBookmarks = new List(); - - if (setting.LoadChromeBookmark) - { - // Add Chrome bookmarks - var chromeBookmarks = new ChromeBookmarkLoader(); - allBookmarks.AddRange(chromeBookmarks.GetBookmarks()); - } - - if (setting.LoadFirefoxBookmark) - { - // Add Firefox bookmarks - var mozBookmarks = new FirefoxBookmarkLoader(); - allBookmarks.AddRange(mozBookmarks.GetBookmarks()); - } - - if (setting.LoadEdgeBookmark) - { - // Add Edge (Chromium) bookmarks - var edgeBookmarks = new EdgeBookmarkLoader(); - allBookmarks.AddRange(edgeBookmarks.GetBookmarks()); - } - - foreach (var browser in setting.CustomChromiumBrowsers) - { - IBookmarkLoader loader = browser.BrowserType switch - { - BrowserType.Chromium => new CustomChromiumBookmarkLoader(browser), - BrowserType.Firefox => new CustomFirefoxBookmarkLoader(browser), - _ => new CustomChromiumBookmarkLoader(browser), - }; - allBookmarks.AddRange(loader.GetBookmarks()); - } - - return allBookmarks.Distinct().ToList(); - } -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomChromiumBookmarkLoader.cs deleted file mode 100644 index 005c83992bf..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomChromiumBookmarkLoader.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Flow.Launcher.Plugin.BrowserBookmark.Models; -using System.Collections.Generic; - -namespace Flow.Launcher.Plugin.BrowserBookmark; - -public class CustomChromiumBookmarkLoader : ChromiumBookmarkLoader -{ - public CustomChromiumBookmarkLoader(CustomBrowser browser) - { - BrowserName = browser.Name; - BrowserDataPath = browser.DataDirectoryPath; - } - public string BrowserDataPath { get; init; } - public string BookmarkFilePath { get; init; } - public string BrowserName { get; init; } - - public override List GetBookmarks() => BrowserDataPath != null ? LoadBookmarks(BrowserDataPath, BrowserName) : LoadBookmarksFromFile(BookmarkFilePath, BrowserName); -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomFirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomFirefoxBookmarkLoader.cs deleted file mode 100644 index d0bb7b0cc71..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomFirefoxBookmarkLoader.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using Flow.Launcher.Plugin.BrowserBookmark.Models; - -namespace Flow.Launcher.Plugin.BrowserBookmark; - -public class CustomFirefoxBookmarkLoader : FirefoxBookmarkLoaderBase -{ - public CustomFirefoxBookmarkLoader(CustomBrowser browser) - { - BrowserName = browser.Name; - BrowserDataPath = browser.DataDirectoryPath; - } - - /// - /// Path to places.sqlite - /// - public string BrowserDataPath { get; init; } - - public string BrowserName { get; init; } - - public override List GetBookmarks() - { - return GetBookmarksFromPath(Path.Combine(BrowserDataPath, "places.sqlite")); - } -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarkLoader.cs deleted file mode 100644 index 40123b022e1..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarkLoader.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Flow.Launcher.Plugin.BrowserBookmark.Models; -using System; -using System.Collections.Generic; -using System.IO; - -namespace Flow.Launcher.Plugin.BrowserBookmark; - -public class EdgeBookmarkLoader : ChromiumBookmarkLoader -{ - private List LoadEdgeBookmarks() - { - var bookmarks = new List(); - var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary")); - - return bookmarks; - } - - public override List GetBookmarks() => LoadEdgeBookmarks(); -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs deleted file mode 100644 index be83f61584f..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ /dev/null @@ -1,341 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Threading.Tasks; -using Flow.Launcher.Plugin.BrowserBookmark.Helper; -using Flow.Launcher.Plugin.BrowserBookmark.Models; -using Microsoft.Data.Sqlite; - -namespace Flow.Launcher.Plugin.BrowserBookmark; - -public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader -{ - private static readonly string ClassName = nameof(FirefoxBookmarkLoaderBase); - - private readonly string _faviconCacheDir; - - protected FirefoxBookmarkLoaderBase() - { - _faviconCacheDir = Main._faviconCacheDir; - } - - public abstract List GetBookmarks(); - - // Updated query - removed favicon_id column - private const string QueryAllBookmarks = """ - SELECT moz_places.url, moz_bookmarks.title - FROM moz_places - INNER JOIN moz_bookmarks ON ( - moz_bookmarks.fk NOT NULL AND moz_bookmarks.title NOT NULL AND moz_bookmarks.fk = moz_places.id - ) - ORDER BY moz_places.visit_count DESC - """; - - protected List GetBookmarksFromPath(string placesPath) - { - // Variable to store bookmark list - var bookmarks = new List(); - - // Return empty list if places.sqlite file doesn't exist - if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath)) - return bookmarks; - - // Try to register file monitoring - try - { - Main.RegisterBookmarkFile(placesPath); - } - catch (Exception ex) - { - Main.Context.API.LogException(ClassName, $"Failed to register Firefox bookmark file monitoring: {placesPath}", ex); - return bookmarks; - } - - var tempDbPath = Path.Combine(_faviconCacheDir, $"tempplaces_{Guid.NewGuid()}.sqlite"); - - try - { - // Use a copy to avoid lock issues with the original file - File.Copy(placesPath, tempDbPath, true); - - // Create the connection string and init the connection - using var dbConnection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly"); - - // Open connection to the database file and execute the query - dbConnection.Open(); - var reader = new SqliteCommand(QueryAllBookmarks, dbConnection).ExecuteReader(); - - // Get results in List format - bookmarks = reader - .Select( - x => new Bookmark( - x["title"] is DBNull ? string.Empty : x["title"].ToString(), - x["url"].ToString(), - "Firefox" - ) - ) - .ToList(); - - // Load favicons after loading bookmarks - if (Main._settings.EnableFavicons) - { - var faviconDbPath = Path.Combine(Path.GetDirectoryName(placesPath), "favicons.sqlite"); - if (File.Exists(faviconDbPath)) - { - Main.Context.API.StopwatchLogInfo(ClassName, $"Load {bookmarks.Count} favicons cost", () => - { - LoadFaviconsFromDb(faviconDbPath, bookmarks); - }); - } - } - - // Close the connection so that we can delete the temporary file - // https://github.com/dotnet/efcore/issues/26580 - SqliteConnection.ClearPool(dbConnection); - dbConnection.Close(); - } - catch (Exception ex) - { - Main.Context.API.LogException(ClassName, $"Failed to load Firefox bookmarks: {placesPath}", ex); - } - - // Delete temporary file - try - { - if (File.Exists(tempDbPath)) - { - File.Delete(tempDbPath); - } - } - catch (Exception ex) - { - Main.Context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex); - } - - return bookmarks; - } - - private void LoadFaviconsFromDb(string dbPath, List bookmarks) - { - FaviconHelper.LoadFaviconsFromDb(_faviconCacheDir, dbPath, (tempDbPath) => - { - // Since some bookmarks may have same favicon id, we need to record them to avoid duplicates - var savedPaths = new ConcurrentDictionary(); - - // Get favicons based on bookmarks concurrently - Parallel.ForEach(bookmarks, bookmark => - { - // Use read-only connection to avoid locking issues - // Do not use pooling so that we do not need to clear pool: https://github.com/dotnet/efcore/issues/26580 - var connection = new SqliteConnection($"Data Source={tempDbPath};Mode=ReadOnly;Pooling=false"); - connection.Open(); - - try - { - if (!Uri.TryCreate(bookmark.Url, UriKind.Absolute, out Uri uri)) - return; - - var domain = uri.Host; - - // Query for latest Firefox version favicon structure - using var cmd = connection.CreateCommand(); - cmd.CommandText = @" - SELECT i.id, i.data - FROM moz_icons i - JOIN moz_icons_to_pages ip ON i.id = ip.icon_id - JOIN moz_pages_w_icons p ON ip.page_id = p.id - WHERE p.page_url LIKE @domain - ORDER BY i.width DESC - LIMIT 1"; - - cmd.Parameters.AddWithValue("@domain", $"%{domain}%"); - - using var reader = cmd.ExecuteReader(); - if (!reader.Read() || reader.IsDBNull(1)) - return; - - var iconId = reader.GetInt64(0).ToString(); - var imageData = (byte[])reader["data"]; - - if (imageData is not { Length: > 0 }) - return; - - // Check if the image data is compressed (GZip) - if (imageData.Length > 2 && imageData[0] == 0x1f && imageData[1] == 0x8b) - { - using var inputStream = new MemoryStream(imageData); - using var gZipStream = new GZipStream(inputStream, CompressionMode.Decompress); - using var outputStream = new MemoryStream(); - gZipStream.CopyTo(outputStream); - imageData = outputStream.ToArray(); - } - - // Convert the image data to WebP format - var webpData = FaviconHelper.TryConvertToWebp(imageData); - if (webpData != null) - { - var faviconPath = Path.Combine(_faviconCacheDir, $"firefox_{domain}_{iconId}.webp"); - - if (savedPaths.TryAdd(faviconPath, true)) - { - FaviconHelper.SaveBitmapData(webpData, faviconPath); - } - - bookmark.FaviconPath = faviconPath; - } - } - catch (Exception ex) - { - Main.Context.API.LogException(ClassName, $"Failed to extract Firefox favicon: {bookmark.Url}", ex); - } - finally - { - // Cache connection and clear pool after all operations to avoid issue: - // ObjectDisposedException: Safe handle has been closed. - connection.Close(); - connection.Dispose(); - } - }); - }); - } -} - -public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase -{ - /// - /// Searches the places.sqlite db and returns all bookmarks - /// - public override List GetBookmarks() - { - var bookmarks = new List(); - bookmarks.AddRange(GetBookmarksFromPath(PlacesPath)); - bookmarks.AddRange(GetBookmarksFromPath(MsixPlacesPath)); - return bookmarks; - } - - /// - /// Path to places.sqlite of Msi installer - /// E.g. C:\Users\{UserName}\AppData\Roaming\Mozilla\Firefox - /// - /// - private static string PlacesPath - { - get - { - var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox"); - return GetProfileIniPath(profileFolderPath); - } - } - - /// - /// Path to places.sqlite of MSIX installer - /// E.g. C:\Users\{UserName}\AppData\Local\Packages\Mozilla.Firefox_n80bbvh6b1yt2\LocalCache\Roaming\Mozilla\Firefox - /// - /// - public static string MsixPlacesPath - { - get - { - var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - var packagesPath = Path.Combine(platformPath, "Packages"); - try - { - // Search for folder with Mozilla.Firefox prefix - var firefoxPackageFolder = Directory.EnumerateDirectories(packagesPath, "Mozilla.Firefox*", - SearchOption.TopDirectoryOnly).FirstOrDefault(); - - // Msix FireFox not installed - if (firefoxPackageFolder == null) return string.Empty; - - var profileFolderPath = Path.Combine(firefoxPackageFolder, @"LocalCache\Roaming\Mozilla\Firefox"); - return GetProfileIniPath(profileFolderPath); - } - catch - { - return string.Empty; - } - } - } - - private static string GetProfileIniPath(string profileFolderPath) - { - var profileIni = Path.Combine(profileFolderPath, @"profiles.ini"); - if (!File.Exists(profileIni)) - return string.Empty; - - // get firefox default profile directory from profiles.ini - using var sReader = new StreamReader(profileIni); - var ini = sReader.ReadToEnd(); - - var lines = ini.Split("\r\n").ToList(); - - var defaultProfileFolderNameRaw = lines.FirstOrDefault(x => x.Contains("Default=") && x != "Default=1") ?? string.Empty; - - if (string.IsNullOrEmpty(defaultProfileFolderNameRaw)) - return string.Empty; - - var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last(); - - var indexOfDefaultProfileAttributePath = lines.IndexOf("Path=" + defaultProfileFolderName); - - /* - Current profiles.ini structure example as of Firefox version 69.0.1 - - [Install736426B0AF4A39CB] - Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile - Locked=1 - - [Profile2] - Name=dummyprofile - IsRelative=0 - Path=C:\t6h2yuq8.dummyprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code. - - [Profile1] - Name=default - IsRelative=1 - Path=Profiles/cydum7q4.default - Default=1 - - [Profile0] - Name=default-release - IsRelative=1 - Path=Profiles/7789f565.default-release - - [General] - StartWithLastProfile=1 - Version=2 - */ - // Seen in the example above, the IsRelative attribute is always above the Path attribute - - var relativePath = Path.Combine(defaultProfileFolderName, "places.sqlite"); - var absolutePath = Path.Combine(profileFolderPath, relativePath); - - // If the index is out of range, it means that the default profile is in a custom location or the file is malformed - // If the profile is in a custom location, we need to check - if (indexOfDefaultProfileAttributePath - 1 < 0 || - indexOfDefaultProfileAttributePath - 1 >= lines.Count) - { - return Directory.Exists(absolutePath) ? absolutePath : relativePath; - } - - var relativeAttribute = lines[indexOfDefaultProfileAttributePath - 1]; - - // See above, the profile is located in a custom location, path is not relative, so IsRelative=0 - return (relativeAttribute == "0" || relativeAttribute == "IsRelative=0") - ? relativePath : absolutePath; - } -} - -public static class Extensions -{ - public static IEnumerable Select(this SqliteDataReader reader, Func projection) - { - while (reader.Read()) - { - yield return projection(reader); - } - } -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs deleted file mode 100644 index 82b0890337c..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Helper/FaviconHelper.cs +++ /dev/null @@ -1,125 +0,0 @@ -using System; -using System.IO; -using SkiaSharp; -using Svg.Skia; - -namespace Flow.Launcher.Plugin.BrowserBookmark.Helper; - -public static class FaviconHelper -{ - private static readonly string ClassName = nameof(FaviconHelper); - - public static void LoadFaviconsFromDb(string faviconCacheDir, string dbPath, Action loadAction) - { - // Use a copy to avoid lock issues with the original file - var tempDbPath = Path.Combine(faviconCacheDir, $"tempfavicons_{Guid.NewGuid()}.db"); - - try - { - File.Copy(dbPath, tempDbPath, true); - } - catch (Exception ex) - { - try - { - if (File.Exists(tempDbPath)) - { - File.Delete(tempDbPath); - } - } - catch (Exception ex1) - { - Main.Context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex1); - } - Main.Context.API.LogException(ClassName, $"Failed to copy favicon DB: {dbPath}", ex); - return; - } - - try - { - loadAction(tempDbPath); - } - catch (Exception ex) - { - Main.Context.API.LogException(ClassName, $"Failed to connect to SQLite: {tempDbPath}", ex); - } - - // Delete temporary file - try - { - File.Delete(tempDbPath); - } - catch (Exception ex) - { - Main.Context.API.LogException(ClassName, $"Failed to delete temporary favicon DB: {tempDbPath}", ex); - } - } - - public static void SaveBitmapData(byte[] imageData, string outputPath) - { - try - { - File.WriteAllBytes(outputPath, imageData); - } - catch (Exception ex) - { - Main.Context.API.LogException(ClassName, $"Failed to save image: {outputPath}", ex); - } - } - - public static byte[] TryConvertToWebp(byte[] data) - { - if (data == null || data.Length == 0) - return null; - - SKBitmap bitmap = null; - - try - { - using (var ms = new MemoryStream(data)) - { - var svg = new SKSvg(); - if (svg.Load(ms) != null && svg.Picture != null) - { - bitmap = new SKBitmap((int)svg.Picture.CullRect.Width, (int)svg.Picture.CullRect.Height); - using (var canvas = new SKCanvas(bitmap)) - { - canvas.Clear(SKColors.Transparent); - canvas.DrawPicture(svg.Picture); - canvas.Flush(); - } - } - } - } - catch { /* Not an SVG */ } - - if (bitmap == null) - { - try - { - bitmap = SKBitmap.Decode(data); - } - catch { /* Not a decodable bitmap */ } - } - - if (bitmap != null) - { - try - { - using var image = SKImage.FromBitmap(bitmap); - if (image is null) - return null; - - using var webp = image.Encode(SKEncodedImageFormat.Webp, 65); - if (webp != null) - return webp.ToArray(); - } - finally - { - bitmap.Dispose(); - } - } - - return null; - } -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/IBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/IBookmarkLoader.cs deleted file mode 100644 index 8a972735275..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/IBookmarkLoader.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Flow.Launcher.Plugin.BrowserBookmark.Models; -using System.Collections.Generic; - -namespace Flow.Launcher.Plugin.BrowserBookmark; - -public interface IBookmarkLoader -{ - public List GetBookmarks(); -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs deleted file mode 100644 index 07ce510fb3e..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ /dev/null @@ -1,275 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Channels; -using System.Threading.Tasks; -using System.Threading; -using System.Windows.Controls; -using Flow.Launcher.Plugin.BrowserBookmark.Commands; -using Flow.Launcher.Plugin.BrowserBookmark.Models; -using Flow.Launcher.Plugin.BrowserBookmark.Views; -using Flow.Launcher.Plugin.SharedCommands; - -namespace Flow.Launcher.Plugin.BrowserBookmark; - -public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable -{ - private static readonly string ClassName = nameof(Main); - - internal static string _faviconCacheDir; - - internal static PluginInitContext Context { get; set; } - - internal static Settings _settings; - - private static List _cachedBookmarks = new(); - - private static bool _initialized = false; - - public void Init(PluginInitContext context) - { - Context = context; - - _settings = context.API.LoadSettingJsonStorage(); - - _faviconCacheDir = Path.Combine( - context.CurrentPluginMetadata.PluginCacheDirectoryPath, - "FaviconCache"); - - try - { - if (Directory.Exists(_faviconCacheDir)) - { - var files = Directory.GetFiles(_faviconCacheDir); - foreach (var file in files) - { - var extension = Path.GetExtension(file); - if (extension is ".db-shm" or ".db-wal" or ".sqlite-shm" or ".sqlite-wal") - { - File.Delete(file); - } - } - } - } - catch (Exception e) - { - Context.API.LogException(ClassName, "Failed to clean up orphaned cache files.", e); - } - - LoadBookmarksIfEnabled(); - } - - private static void LoadBookmarksIfEnabled() - { - if (Context.CurrentPluginMetadata.Disabled) - { - // Don't load or monitor files if disabled - return; - } - - // Validate the cache directory before loading all bookmarks because Flow needs this directory to storage favicons - FilesFolders.ValidateDirectory(_faviconCacheDir); - - _cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings); - _ = MonitorRefreshQueueAsync(); - _initialized = true; - } - - public List Query(Query query) - { - // For when the plugin being previously disabled and is now re-enabled - if (!_initialized) - { - LoadBookmarksIfEnabled(); - } - - string param = query.Search.TrimStart(); - - // Should top results be returned? (true if no search parameters have been passed) - var topResults = string.IsNullOrEmpty(param); - - if (!topResults) - { - // Since we mixed chrome and firefox bookmarks, we should order them again - return _cachedBookmarks - .Select( - c => new Result - { - Title = c.Name, - SubTitle = c.Url, - IcoPath = !string.IsNullOrEmpty(c.FaviconPath) && File.Exists(c.FaviconPath) - ? c.FaviconPath - : @"Images\bookmark.png", - Score = BookmarkLoader.MatchProgram(c, param).Score, - Action = _ => - { - Context.API.OpenUrl(c.Url); - - return true; - }, - ContextData = new BookmarkAttributes { Url = c.Url } - } - ) - .Where(r => r.Score > 0) - .ToList(); - } - else - { - return _cachedBookmarks - .Select( - c => new Result - { - Title = c.Name, - SubTitle = c.Url, - IcoPath = !string.IsNullOrEmpty(c.FaviconPath) && File.Exists(c.FaviconPath) - ? c.FaviconPath - : @"Images\bookmark.png", - Score = 5, - Action = _ => - { - Context.API.OpenUrl(c.Url); - return true; - }, - ContextData = new BookmarkAttributes { Url = c.Url } - } - ) - .ToList(); - } - } - - private static readonly Channel _refreshQueue = Channel.CreateBounded(1); - - private static readonly SemaphoreSlim _fileMonitorSemaphore = new(1, 1); - - private static async Task MonitorRefreshQueueAsync() - { - if (_fileMonitorSemaphore.CurrentCount < 1) - { - return; - } - await _fileMonitorSemaphore.WaitAsync(); - var reader = _refreshQueue.Reader; - while (await reader.WaitToReadAsync()) - { - if (reader.TryRead(out _)) - { - ReloadAllBookmarks(false); - } - } - _fileMonitorSemaphore.Release(); - } - - private static readonly List Watchers = new(); - - internal static void RegisterBookmarkFile(string path) - { - var directory = Path.GetDirectoryName(path); - if (!Directory.Exists(directory) || !File.Exists(path)) - { - return; - } - if (Watchers.Any(x => x.Path.Equals(directory, StringComparison.OrdinalIgnoreCase))) - { - return; - } - - var watcher = new FileSystemWatcher(directory!) - { - Filter = Path.GetFileName(path), - NotifyFilter = NotifyFilters.FileName | - NotifyFilters.LastWrite | - NotifyFilters.Size - }; - - watcher.Changed += static (_, _) => - { - _refreshQueue.Writer.TryWrite(default); - }; - - watcher.Renamed += static (_, _) => - { - _refreshQueue.Writer.TryWrite(default); - }; - - watcher.EnableRaisingEvents = true; - - Watchers.Add(watcher); - } - - public void ReloadData() - { - ReloadAllBookmarks(); - } - - public static void ReloadAllBookmarks(bool disposeFileWatchers = true) - { - _cachedBookmarks.Clear(); - if (disposeFileWatchers) - DisposeFileWatchers(); - LoadBookmarksIfEnabled(); - } - - public string GetTranslatedPluginTitle() - { - return Localize.flowlauncher_plugin_browserbookmark_plugin_name(); - } - - public string GetTranslatedPluginDescription() - { - return Localize.flowlauncher_plugin_browserbookmark_plugin_description(); - } - - public Control CreateSettingPanel() - { - return new SettingsControl(_settings); - } - - public List LoadContextMenus(Result selectedResult) - { - return new List() - { - new() - { - Title = Localize.flowlauncher_plugin_browserbookmark_copyurl_title(), - SubTitle = Localize.flowlauncher_plugin_browserbookmark_copyurl_subtitle(), - Action = _ => - { - try - { - Context.API.CopyToClipboard(((BookmarkAttributes)selectedResult.ContextData).Url); - - return true; - } - catch (Exception e) - { - Context.API.LogException(ClassName, "Failed to set url in clipboard", e); - Context.API.ShowMsgError(Localize.flowlauncher_plugin_browserbookmark_copy_failed()); - return false; - } - }, - IcoPath = @"Images\copylink.png", - Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8") - } - }; - } - - internal class BookmarkAttributes - { - internal string Url { get; set; } - } - - public void Dispose() - { - DisposeFileWatchers(); - } - - private static void DisposeFileWatchers() - { - foreach (var watcher in Watchers) - { - watcher.Dispose(); - } - Watchers.Clear(); - } -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs deleted file mode 100644 index caab16b65e8..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; - -namespace Flow.Launcher.Plugin.BrowserBookmark.Models; - -// Source may be important in the future -public record Bookmark(string Name, string Url, string Source = "") -{ - public override int GetHashCode() - { - var hashName = Name?.GetHashCode() ?? 0; - var hashUrl = Url?.GetHashCode() ?? 0; - return hashName ^ hashUrl; - } - - public virtual bool Equals(Bookmark other) - { - return other != null && Name == other.Name && Url == other.Url; - } - - public List CustomBrowsers { get; set; } = new(); - public string FaviconPath { get; set; } = string.Empty; -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs deleted file mode 100644 index a0041e0d6a0..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.ObjectModel; - -namespace Flow.Launcher.Plugin.BrowserBookmark.Models; - -public class Settings : BaseModel -{ - public bool OpenInNewBrowserWindow { get; set; } = true; - - public string BrowserPath { get; set; } - - public bool EnableFavicons { get; set; } = false; - - public bool LoadChromeBookmark { get; set; } = true; - public bool LoadFirefoxBookmark { get; set; } = true; - public bool LoadEdgeBookmark { get; set; } = true; - - public ObservableCollection CustomChromiumBrowsers { get; set; } = new(); -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml.cs deleted file mode 100644 index bff2967d6cf..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Flow.Launcher.Plugin.BrowserBookmark.Models; -using System.Windows; -using System.Windows.Input; -using System.Windows.Forms; - -namespace Flow.Launcher.Plugin.BrowserBookmark.Views; - -/// -/// Interaction logic for CustomBrowserSetting.xaml -/// -public partial class CustomBrowserSettingWindow : Window -{ - private CustomBrowser _currentCustomBrowser; - public CustomBrowserSettingWindow(CustomBrowser browser) - { - InitializeComponent(); - _currentCustomBrowser = browser; - DataContext = new CustomBrowser - { - Name = browser.Name, - DataDirectoryPath = browser.DataDirectoryPath, - BrowserType = browser.BrowserType, - }; - } - - private void ConfirmEditCustomBrowser(object sender, RoutedEventArgs e) - { - CustomBrowser editBrowser = (CustomBrowser)DataContext; - _currentCustomBrowser.Name = editBrowser.Name; - _currentCustomBrowser.DataDirectoryPath = editBrowser.DataDirectoryPath; - _currentCustomBrowser.BrowserType = editBrowser.BrowserType; - DialogResult = true; - Close(); - } - - private void CancelEditCustomBrowser(object sender, RoutedEventArgs e) - { - Close(); - } - - private void WindowKeyDown(object sender, System.Windows.Input.KeyEventArgs e) - { - if (e.Key == Key.Enter) - { - ConfirmEditCustomBrowser(sender, e); - } - } - - private void OnSelectPathClick(object sender, RoutedEventArgs e) - { - var dialog = new FolderBrowserDialog(); - dialog.ShowDialog(); - CustomBrowser editBrowser = (CustomBrowser)DataContext; - editBrowser.DataDirectoryPath = dialog.SelectedPath; - } -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs deleted file mode 100644 index 1ee6b5c4551..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System.Windows; -using System.Windows.Input; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.ComponentModel; -using Flow.Launcher.Plugin.BrowserBookmark.Models; - -namespace Flow.Launcher.Plugin.BrowserBookmark.Views; - -[INotifyPropertyChanged] -public partial class SettingsControl -{ - public Settings Settings { get; } - public CustomBrowser SelectedCustomBrowser { get; set; } - - public SettingsControl(Settings settings) - { - Settings = settings; - InitializeComponent(); - } - - public bool LoadChromeBookmark - { - get => Settings.LoadChromeBookmark; - set - { - Settings.LoadChromeBookmark = value; - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } - - public bool LoadFirefoxBookmark - { - get => Settings.LoadFirefoxBookmark; - set - { - Settings.LoadFirefoxBookmark = value; - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } - - public bool LoadEdgeBookmark - { - get => Settings.LoadEdgeBookmark; - set - { - Settings.LoadEdgeBookmark = value; - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } - - public bool OpenInNewBrowserWindow - { - get => Settings.OpenInNewBrowserWindow; - set - { - Settings.OpenInNewBrowserWindow = value; - OnPropertyChanged(); - } - } - - private void NewCustomBrowser(object sender, RoutedEventArgs e) - { - var newBrowser = new CustomBrowser(); - var window = new CustomBrowserSettingWindow(newBrowser); - window.ShowDialog(); - if (newBrowser is not - { - Name: null, - DataDirectoryPath: null - }) - { - Settings.CustomChromiumBrowsers.Add(newBrowser); - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } - - private void DeleteCustomBrowser(object sender, RoutedEventArgs e) - { - if (CustomBrowsers.SelectedItem is CustomBrowser selectedCustomBrowser) - { - Settings.CustomChromiumBrowsers.Remove(selectedCustomBrowser); - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } - - private void MouseDoubleClickOnSelectedCustomBrowser(object sender, MouseButtonEventArgs e) - { - EditSelectedCustomBrowser(); - } - - private void Others_Click(object sender, RoutedEventArgs e) - { - CustomBrowsersList.Visibility = CustomBrowsersList.Visibility switch - { - Visibility.Collapsed => Visibility.Visible, - _ => Visibility.Collapsed - }; - } - - private void EditCustomBrowser(object sender, RoutedEventArgs e) - { - EditSelectedCustomBrowser(); - } - - private void EditSelectedCustomBrowser() - { - if (SelectedCustomBrowser is null) - return; - - var window = new CustomBrowserSettingWindow(SelectedCustomBrowser); - var result = window.ShowDialog() ?? false; - if (result) - { - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json deleted file mode 100644 index 30e34f62dc8..00000000000 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ID": "0ECADE17459B49F587BF81DC3A125110", - "ActionKeyword": "b", - "Name": "Browser Bookmarks", - "Description": "Search your browser bookmarks", - "Author": "qianlifeng, Ioannis G.", - "Version": "1.0.0", - "Language": "csharp", - "Website": "https://github.com/Flow-Launcher/Flow.Launcher", - "ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll", - "IcoPath": "Images\\bookmark.png" -} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Flow.Launcher.Plugin.BrowserBookmarks.csproj similarity index 86% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Flow.Launcher.Plugin.BrowserBookmarks.csproj index 9cb2469d9d7..da1054f5179 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Flow.Launcher.Plugin.BrowserBookmarks.csproj @@ -1,4 +1,4 @@ - + Library @@ -6,8 +6,8 @@ true {9B130CC5-14FB-41FF-B310-0A95B6894C37} Properties - Flow.Launcher.Plugin.BrowserBookmark - Flow.Launcher.Plugin.BrowserBookmark + Flow.Launcher.Plugin.BrowserBookmarks + Flow.Launcher.Plugin.BrowserBookmarks true false false @@ -19,7 +19,7 @@ true portable false - ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.BrowserBookmark\ + ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.BrowserBookmarks\ DEBUG;TRACE prompt 4 @@ -29,7 +29,7 @@ pdbonly true - ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.BrowserBookmark\ + ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.BrowserBookmarks\ TRACE prompt 4 @@ -57,7 +57,7 @@ $(OutputPath)runtimes\osx-arm64; $(OutputPath)runtimes\osx-x64; $(OutputPath)runtimes\win-arm; - $(OutputPath)runtimes\win-arm64;"/> + $(OutputPath)runtimes\win-arm64;" /> @@ -80,34 +80,40 @@ $(PublishDir)runtimes\osx-arm64; $(PublishDir)runtimes\osx-x64; $(PublishDir)runtimes\win-arm; - $(PublishDir)runtimes\win-arm64;"/> + $(PublishDir)runtimes\win-arm64;" /> - + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + PreserveNewest - - - - - + - - PreserveNewest - PreserveNewest + + + + - + - + - + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Flow.Launcher.Plugin.BrowserBookmarks.sln b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Flow.Launcher.Plugin.BrowserBookmarks.sln new file mode 100644 index 00000000000..2c79b0167d8 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Flow.Launcher.Plugin.BrowserBookmarks.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flow.Launcher.Plugin.BrowserBookmarks", "Flow.Launcher.Plugin.BrowserBookmarks.csproj", "{BE047398-4D54-9D16-720E-AB0002E872C4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BE047398-4D54-9D16-720E-AB0002E872C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE047398-4D54-9D16-720E-AB0002E872C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE047398-4D54-9D16-720E-AB0002E872C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE047398-4D54-9D16-720E-AB0002E872C4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2FA6CF2E-9DDE-42DD-B371-5DADB587392E} + EndGlobalSection +EndGlobal diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/bookmark.png b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Images/bookmark.png similarity index 100% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/bookmark.png rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Images/bookmark.png diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Images/copylink.png similarity index 100% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Images/copylink.png diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ar.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ar.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ar.xaml index cb9eca6993f..7d80d837bb1 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ar.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ar.xaml @@ -1,33 +1,33 @@ - - - - - إشارات المتصفح - ابحث في إشارات المتصفح - - - Failed to set url in clipboard - - - بيانات الإشارات المرجعية - فتح الإشارات المرجعية في: - نافذة جديدة - علامة تبويب جديدة - تحديد المتصفح من المسار: - اختر - نسخ الرابط - نسخ رابط الإشارة المرجعية إلى الحافظة - تحميل المتصفح من: - اسم المتصفح - مسار دليل البيانات - إضافة إشارة مرجعية - تعديل إشارة مرجعية - حذف إشارة مرجعية - تصفح - أخرى - محرك المتصفح - إذا كنت لا تستخدم Chrome أو Firefox أو Edge، أو كنت تستخدم نسختهم المحمولة، ستحتاج إلى إضافة دليل بيانات الإشارات المرجعية وتحديد محرك المتصفح الصحيح لجعل هذه الإضافة تعمل. - على سبيل المثال: محرك Brave هو Chromium؛ وموقع بيانات الإشارات المرجعية الافتراضي هو: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". بالنسبة لمحرك Firefox، دليل الإشارات المرجعية هو مجلد userdata الذي يحتوي على ملف places.sqlite. - Load favicons (can be time consuming during startup) - - + + + + + إشارات المتصفح + ابحث في إشارات المتصفح + + + Failed to set url in clipboard + + + بيانات الإشارات المرجعية + فتح الإشارات المرجعية في: + نافذة جديدة + علامة تبويب جديدة + تحديد المتصفح من المسار: + اختر + نسخ الرابط + نسخ رابط الإشارة المرجعية إلى الحافظة + تحميل المتصفح من: + اسم المتصفح + مسار دليل البيانات + إضافة إشارة مرجعية + تعديل إشارة مرجعية + حذف إشارة مرجعية + تصفح + أخرى + محرك المتصفح + إذا كنت لا تستخدم Chrome أو Firefox أو Edge، أو كنت تستخدم نسختهم المحمولة، ستحتاج إلى إضافة دليل بيانات الإشارات المرجعية وتحديد محرك المتصفح الصحيح لجعل هذه الإضافة تعمل. + على سبيل المثال: محرك Brave هو Chromium؛ وموقع بيانات الإشارات المرجعية الافتراضي هو: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". بالنسبة لمحرك Firefox، دليل الإشارات المرجعية هو مجلد userdata الذي يحتوي على ملف places.sqlite. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/cs.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/cs.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/cs.xaml index 382418336a4..3f92e7c9a6f 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/cs.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/cs.xaml @@ -1,33 +1,33 @@ - - - - - Záložky prohlížeče - Hledat záložky v prohlížeči - - - Failed to set url in clipboard - - - Data záložek - Otevřít záložky v: - Nové okno - Nová záložka - Nastavte cestu k prohlížeči: - Vybrat - Kopírovat URL - Zkopírovat adresu URL záložky do schránky - Načíst prohlížeč z: - Název prohlížeče - Cesta ke složce dat - Přidat - Editovat - Smazat - Procházet - Jiné - Jádro webového prohlížeče - Pokud nepoužíváte prohlížeč Chrome, Firefox nebo Edge nebo používáte přenosnou verzi prohlížeče Chrome, Firefox nebo Edge, musíte přidat složku záložek a vybrat správné jádro prohlížeče, aby tento doplněk fungoval. - Například: prohlížeč Brave má jádro Chromium; výchozí umístění pro data záložek je: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". V případě jádra Firefox je složkou záložek složka userdata, která obsahuje soubor places.sqlite. - Load favicons (can be time consuming during startup) - - + + + + + Záložky prohlížeče + Hledat záložky v prohlížeči + + + Failed to set url in clipboard + + + Data záložek + Otevřít záložky v: + Nové okno + Nová záložka + Nastavte cestu k prohlížeči: + Vybrat + Kopírovat URL + Zkopírovat adresu URL záložky do schránky + Načíst prohlížeč z: + Název prohlížeče + Cesta ke složce dat + Přidat + Editovat + Smazat + Procházet + Jiné + Jádro webového prohlížeče + Pokud nepoužíváte prohlížeč Chrome, Firefox nebo Edge nebo používáte přenosnou verzi prohlížeče Chrome, Firefox nebo Edge, musíte přidat složku záložek a vybrat správné jádro prohlížeče, aby tento doplněk fungoval. + Například: prohlížeč Brave má jádro Chromium; výchozí umístění pro data záložek je: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". V případě jádra Firefox je složkou záložek složka userdata, která obsahuje soubor places.sqlite. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/da.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/da.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/da.xaml index 68a8b7a6680..93f1352dabf 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/da.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/da.xaml @@ -1,33 +1,33 @@ - - - - - Browser Bookmarks - Search your browser bookmarks - - - Failed to set url in clipboard - - - Bookmark Data - Open bookmarks in: - New window - New tab - Set browser from path: - Choose - Copy url - Copy the bookmark's url to clipboard - Load Browser From: - Browser Name - Data Directory Path - Tilføj - Rediger - Slet - Browse - Others - Browser Engine - If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. - For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. - Load favicons (can be time consuming during startup) - - + + + + + Browser Bookmarks + Search your browser bookmarks + + + Failed to set url in clipboard + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Tilføj + Rediger + Slet + Browse + Others + Browser Engine + If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. + For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/de.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/de.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/de.xaml index 68bb924113d..ea72043704c 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/de.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/de.xaml @@ -1,34 +1,34 @@ - - - - - Browser-Lesezeichen - Ihre Browser-Lesezeichen durchsuchen - - - URL in Zwischenablage konnte nicht festgelegt werden - - - Lesezeichen-Daten - Lesezeichen öffnen in: - Neues Fenster - Neuer Tab - Browser aus Pfad festlegen: - Wählen - URL kopieren - URL des Lesezeichens in Zwischenablage kopieren - Browser laden aus: - Browser-Name - Pfad zu Datenverzeichnis - Hinzufügen - Bearbeiten - Löschen - Durchsuchen - Andere - Browser-Engine - Wenn Sie nicht Chrome, Firefox oder Edge verwenden oder deren portable Version nutzen, müssen Sie das Lesezeichen-Datenverzeichnis hinzufügen und die richtige Browser-Engine auswählen, damit dieses Plug-in funktioniert. - Zum Beispiel: Die Engine von Brave ist Chromium, und deren Standardspeicherort der Lesezeichen-Daten ist: -%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Bei der Firefox-Engine ist das Lesezeichenverzeichnis der Ordner userdata, der die Datei places.sqlite enthält. - Favicons laden (kann während des Starts zeitaufwendig sein) - - + + + + + Browser-Lesezeichen + Ihre Browser-Lesezeichen durchsuchen + + + URL in Zwischenablage konnte nicht festgelegt werden + + + Lesezeichen-Daten + Lesezeichen öffnen in: + Neues Fenster + Neuer Tab + Browser aus Pfad festlegen: + Wählen + URL kopieren + URL des Lesezeichens in Zwischenablage kopieren + Browser laden aus: + Browser-Name + Pfad zu Datenverzeichnis + Hinzufügen + Bearbeiten + Löschen + Durchsuchen + Andere + Browser-Engine + Wenn Sie nicht Chrome, Firefox oder Edge verwenden oder deren portable Version nutzen, müssen Sie das Lesezeichen-Datenverzeichnis hinzufügen und die richtige Browser-Engine auswählen, damit dieses Plug-in funktioniert. + Zum Beispiel: Die Engine von Brave ist Chromium, und deren Standardspeicherort der Lesezeichen-Daten ist: +%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Bei der Firefox-Engine ist das Lesezeichenverzeichnis der Ordner userdata, der die Datei places.sqlite enthält. + Favicons laden (kann während des Starts zeitaufwendig sein) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/en.xaml similarity index 50% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/en.xaml index 56471417309..d0ea98553ec 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/en.xaml @@ -5,31 +5,35 @@ Browser Bookmarks - Search your browser bookmarks + Search your browser bookmarks, retrieve favicons from the web. - Failed to set url in clipboard + Failed to copy URL to clipboard + Copy URL + Copy the bookmark's URL to clipboard - Bookmark Data - Open bookmarks in: - New window - New tab - Set browser from path: - Choose - Copy url - Copy the bookmark's url to clipboard - Load Browser From: - Browser Name - Data Directory Path + Load bookmarks from: + Others... Add Edit Delete + Enable favicons + Fetch missing favicons from the web + + + Custom Browser Setting + Browser Name + Data / Profile Path Browse - Others - Browser Engine - If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. - For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. - Load favicons (can be time consuming during startup) + Enter your custom browser information here. + Chromium-based browsers: enter the path to your 'Bookmarks' file. + Firefox-based browsers: enter the path to your 'places.sqlite' file. + + + Invalid or unsupported profile directory. + Detected: Chromium-based browser + Detected: Firefox-based browser + Please select a data directory. \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/es-419.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es-419.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/es-419.xaml index 859a0ea0ec4..d037cdabccf 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es-419.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/es-419.xaml @@ -1,33 +1,33 @@ - - - - - Marcadores del Navegador - Busca en los marcadores de tu navegador - - - Failed to set url in clipboard - - - Datos de Marcadores - Abrir marcadores en: - Nueva ventana - Nueva pestaña - Establecer navegador desde ruta: - Elegir - Copiar url - Copiar la url del marcador al portapapeles - Cargar navegador desde: - Nombre del Navegador - Ruta del Directorio de Datos - Añadir - Editar - Eliminar - Browse - Others - Browser Engine - If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. - For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. - Load favicons (can be time consuming during startup) - - + + + + + Marcadores del Navegador + Busca en los marcadores de tu navegador + + + Failed to set url in clipboard + + + Datos de Marcadores + Abrir marcadores en: + Nueva ventana + Nueva pestaña + Establecer navegador desde ruta: + Elegir + Copiar url + Copiar la url del marcador al portapapeles + Cargar navegador desde: + Nombre del Navegador + Ruta del Directorio de Datos + Añadir + Editar + Eliminar + Browse + Others + Browser Engine + If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. + For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/es.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/es.xaml index e0beb7a78cf..553a34fc262 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/es.xaml @@ -1,33 +1,33 @@ - - - - - Marcadores del navegador - Busca en los marcadores del navegador - - - No se ha podido establecer la url en el portapeles - - - Datos del marcador - Abrir marcadores en: - Nueva ventana - Nueva pestaña - Establecer navegador desde ruta: - Elegir - Copiar url - Copia la url del marcador al portapapeles - Cargar navegador desde: - Nombre del navegador - Ruta del directorio de datos - Añadir - Editar - Eliminar - Navegar - Otros - Motor del navegador - Si no está utilizando Chrome, Firefox o Edge, o si está utilizando su versión portable, debe añadir el directorio de datos de los marcadores y seleccionar el motor del navegador correcto para que este complemento funcione. - Por ejemplo: El motor de Brave es Chromium; y la ubicación por defecto de los datos de los marcadores es: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Para el motor de Firefox, el directorio de los marcadores es la carpeta de datos del usuario que contiene el archivo places.sqlite. - Cargar favicons (puede llevar mucho tiempo durante el arranque) - - + + + + + Marcadores del navegador + Busca en los marcadores del navegador + + + No se ha podido establecer la url en el portapeles + + + Datos del marcador + Abrir marcadores en: + Nueva ventana + Nueva pestaña + Establecer navegador desde ruta: + Elegir + Copiar url + Copia la url del marcador al portapapeles + Cargar navegador desde: + Nombre del navegador + Ruta del directorio de datos + Añadir + Editar + Eliminar + Navegar + Otros + Motor del navegador + Si no está utilizando Chrome, Firefox o Edge, o si está utilizando su versión portable, debe añadir el directorio de datos de los marcadores y seleccionar el motor del navegador correcto para que este complemento funcione. + Por ejemplo: El motor de Brave es Chromium; y la ubicación por defecto de los datos de los marcadores es: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Para el motor de Firefox, el directorio de los marcadores es la carpeta de datos del usuario que contiene el archivo places.sqlite. + Cargar favicons (puede llevar mucho tiempo durante el arranque) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/fr.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/fr.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/fr.xaml index de10d0f19bc..2b4d6311dc6 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/fr.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/fr.xaml @@ -1,33 +1,33 @@ - - - - - Favoris du Navigateur - Rechercher dans les favoris de votre navigateur - - - Impossible de mettre l'url dans le presse-papiers - - - Données des favoris - Ouvrir les favoris dans : - Nouvelle fenêtre - Nouvel onglet - Définir le navigateur à partir du chemin : - Choisir - Copier l'url - Copier l'Url du favori dans le presse-papier - Charger le navigateur depuis : - Nom du navigateur - Répertoire des Données - Ajouter - Modifier - Supprimer - Parcourir - Autres - Navigateur - Si vous n'utilisez pas Chrome, Firefox ou Edge, ou que vous utilisez leur version portable, vous devez ajouter un dossier de favoris et sélectionner le bon navigateur pour que ce plugin fonctionne. - Par exemple : le moteur de Brave est Chromium ; et son emplacement par défaut des favoris est : "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Pour le moteur Firefox, le dossier des favoris est le dossier userdata contenant le fichier places.sqlite. - Charger les favicons (peut prendre du temps au démarrage) - - + + + + + Favoris du Navigateur + Rechercher dans les favoris de votre navigateur + + + Impossible de mettre l'url dans le presse-papiers + + + Données des favoris + Ouvrir les favoris dans : + Nouvelle fenêtre + Nouvel onglet + Définir le navigateur à partir du chemin : + Choisir + Copier l'url + Copier l'Url du favori dans le presse-papier + Charger le navigateur depuis : + Nom du navigateur + Répertoire des Données + Ajouter + Modifier + Supprimer + Parcourir + Autres + Navigateur + Si vous n'utilisez pas Chrome, Firefox ou Edge, ou que vous utilisez leur version portable, vous devez ajouter un dossier de favoris et sélectionner le bon navigateur pour que ce plugin fonctionne. + Par exemple : le moteur de Brave est Chromium ; et son emplacement par défaut des favoris est : "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Pour le moteur Firefox, le dossier des favoris est le dossier userdata contenant le fichier places.sqlite. + Charger les favicons (peut prendre du temps au démarrage) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/he.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/he.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/he.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/he.xaml index 90751bb6f0b..6767d66754a 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/he.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/he.xaml @@ -1,33 +1,33 @@ - - - - - סימניות דפדפן - חפש בסימניות הדפדפן שלך - - - Failed to set url in clipboard - - - נתוני סימניות - פתח סימניות ב: - חלון חדש - לשונית חדשה - הגדר דפדפן מנתיב: - בחר - העתק כתובת - העתק את כתובת הסימנייה ללוח - טען דפדפן מ: - שם הדפדפן - נתיב ספריית הנתונים - הוסף - ערוך - מחק - עיין - אחרים - מנוע דפדפן - אם אינך משתמש ב-Chrome, Firefox או Edge, או שאתה משתמש בגרסה הניידת שלהם, עליך להוסיף את ספריית נתוני הסימניות ולבחור את מנוע הדפדפן המתאים כדי שהתוסף יעבוד. - לדוגמה: המנוע של Brave הוא Chromium, ומיקום ברירת המחדל של נתוני הסימניות שלו הוא: %LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData. עבור מנוע Firefox, ספריית הסימניות היא תיקיית המשתמש שמכילה את הקובץ places.sqlite. - Load favicons (can be time consuming during startup) - - + + + + + סימניות דפדפן + חפש בסימניות הדפדפן שלך + + + Failed to set url in clipboard + + + נתוני סימניות + פתח סימניות ב: + חלון חדש + לשונית חדשה + הגדר דפדפן מנתיב: + בחר + העתק כתובת + העתק את כתובת הסימנייה ללוח + טען דפדפן מ: + שם הדפדפן + נתיב ספריית הנתונים + הוסף + ערוך + מחק + עיין + אחרים + מנוע דפדפן + אם אינך משתמש ב-Chrome, Firefox או Edge, או שאתה משתמש בגרסה הניידת שלהם, עליך להוסיף את ספריית נתוני הסימניות ולבחור את מנוע הדפדפן המתאים כדי שהתוסף יעבוד. + לדוגמה: המנוע של Brave הוא Chromium, ומיקום ברירת המחדל של נתוני הסימניות שלו הוא: %LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData. עבור מנוע Firefox, ספריית הסימניות היא תיקיית המשתמש שמכילה את הקובץ places.sqlite. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/it.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/it.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/it.xaml index 32216f3505d..97d506aba7c 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/it.xaml @@ -1,33 +1,33 @@ - - - - - Segnalibri del Browser - Cerca nei segnalibri del tuo browser - - - Failed to set url in clipboard - - - Dati del segnalibro - Apri preferiti in: - Nuova finestra - Nuova scheda - Imposta il browser dal percorso: - Scegli - Copia url - Copia l'url del segnalibro negli appunti - Carica Il Browser Da: - Nome del browser - Percorso cartella Data - Aggiungi - Modifica - Cancella - Sfoglia - Altri - Motore di Navigazione - Se non si utilizza Chrome, Firefox o Edge, o si utilizza la loro versione portatile, è necessario aggiungere la cartella dei segnalibri e selezionare il motore di navigazione corretto per far funzionare questo plugin. - Per esempio: il motore di Brave è Chromium, e la sua posizione predefinita dei segnalibri è: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Per il motore di Firefox, la directory dei segnalibri è la cartella dei dati utente che contiene il file places.sqlite. - Load favicons (can be time consuming during startup) - - + + + + + Segnalibri del Browser + Cerca nei segnalibri del tuo browser + + + Failed to set url in clipboard + + + Dati del segnalibro + Apri preferiti in: + Nuova finestra + Nuova scheda + Imposta il browser dal percorso: + Scegli + Copia url + Copia l'url del segnalibro negli appunti + Carica Il Browser Da: + Nome del browser + Percorso cartella Data + Aggiungi + Modifica + Cancella + Sfoglia + Altri + Motore di Navigazione + Se non si utilizza Chrome, Firefox o Edge, o si utilizza la loro versione portatile, è necessario aggiungere la cartella dei segnalibri e selezionare il motore di navigazione corretto per far funzionare questo plugin. + Per esempio: il motore di Brave è Chromium, e la sua posizione predefinita dei segnalibri è: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Per il motore di Firefox, la directory dei segnalibri è la cartella dei dati utente che contiene il file places.sqlite. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ja.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ja.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ja.xaml index 6700bce1902..300b6ee6f8e 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ja.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ja.xaml @@ -1,33 +1,33 @@ - - - - - ブラウザブックマーク - ブラウザのブックマークを検索します - - - Failed to set url in clipboard - - - Bookmark Data - Open bookmarks in: - New window - New tab - Set browser from path: - Choose - URLをコピー - ブックマークのURLをクリップボードにコピー - 次のブラウザから読み込む: - ブラウザ名 - データフォルダのパス - 追加 - 編集 - 削除 - 選択 - その他のブラウザ - ブラウザー エンジン - Chrome、Firefox、Edgeを使用していない場合、またはそれらのポータブル版を使用している場合、 このプラグインを動作させるには、ブックマークデータのフォルダを指定し、正しいブラウザエンジンを選択する必要があります。 - 例: BraveのエンジンはChromiumで、デフォルトのブックマークデータは以下の場所にあります: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData"。 Firefox エンジンの場合、bookmarks ディレクトリは userdata フォルダにあって、places.sqlite ファイルが格納されています。 - ファビコンを読み込む (起動時に時間がかかる場合があります) - - + + + + + ブラウザブックマーク + ブラウザのブックマークを検索します + + + Failed to set url in clipboard + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + URLをコピー + ブックマークのURLをクリップボードにコピー + 次のブラウザから読み込む: + ブラウザ名 + データフォルダのパス + 追加 + 編集 + 削除 + 選択 + その他のブラウザ + ブラウザー エンジン + Chrome、Firefox、Edgeを使用していない場合、またはそれらのポータブル版を使用している場合、 このプラグインを動作させるには、ブックマークデータのフォルダを指定し、正しいブラウザエンジンを選択する必要があります。 + 例: BraveのエンジンはChromiumで、デフォルトのブックマークデータは以下の場所にあります: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData"。 Firefox エンジンの場合、bookmarks ディレクトリは userdata フォルダにあって、places.sqlite ファイルが格納されています。 + ファビコンを読み込む (起動時に時間がかかる場合があります) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ko.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ko.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ko.xaml index fd381d4ee72..2e4cf033d37 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ko.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ko.xaml @@ -1,33 +1,33 @@ - - - - - 브라우저 북마크 - 브라우저의 북마크 검색 - - - Failed to set url in clipboard - - - 북마크 데이터 - Open bookmarks in: - 새 창 - 새 탭 - Set browser from path: - 선택 - URL 복사 - 북마크의 URL을 클립보드에 복사 - 데이터를 가져올 브라우저: - 브라우저 이름 - 데이터 디렉토리 위치 - 추가 - 편집 - 삭제 - 찾아보기 - Others - 브라우저 엔진 - 크롬, 파이어폭스 또는 엣지를 사용하지 않거나 이들의 포터블 버전을 사용하고 있다면, 이 플러그인을 작동시키기 위해 북마크 데이터 디렉토리를 추가하고 올바른 브라우저 엔진을 선택해야 합니다. - 예를 들어: Brave의 엔진은 Chromium이며, 기본 북마크 데이터 위치는 "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData"입니다. Firefox 엔진의 경우, 북마크 위치는 places.sqlite 파일이 포함된 userdata 폴더입니다. - Load favicons (can be time consuming during startup) - - + + + + + 브라우저 북마크 + 브라우저의 북마크 검색 + + + Failed to set url in clipboard + + + 북마크 데이터 + Open bookmarks in: + 새 창 + 새 탭 + Set browser from path: + 선택 + URL 복사 + 북마크의 URL을 클립보드에 복사 + 데이터를 가져올 브라우저: + 브라우저 이름 + 데이터 디렉토리 위치 + 추가 + 편집 + 삭제 + 찾아보기 + Others + 브라우저 엔진 + 크롬, 파이어폭스 또는 엣지를 사용하지 않거나 이들의 포터블 버전을 사용하고 있다면, 이 플러그인을 작동시키기 위해 북마크 데이터 디렉토리를 추가하고 올바른 브라우저 엔진을 선택해야 합니다. + 예를 들어: Brave의 엔진은 Chromium이며, 기본 북마크 데이터 위치는 "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData"입니다. Firefox 엔진의 경우, 북마크 위치는 places.sqlite 파일이 포함된 userdata 폴더입니다. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/nb.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nb.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/nb.xaml index e3650ed269e..211cea28996 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nb.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/nb.xaml @@ -1,33 +1,33 @@ - - - - - Nettleserbokmerker - Søk i nettleserbokmerker - - - Failed to set url in clipboard - - - Bokmerkedata - Åpne bokmerker i: - Nytt vindu - Ny fane - Angi nettleser fra banen: - Velg - Kopier nettadresse - Kopier bokmerkets nettadresse til utklippstavlen - Last nettleser fra: - Nettlesernavn - Sti til datakatalog - Legg til - Rediger - Slett - Bla - Andre - Nettlesermotor - Hvis du ikke bruker Chrome, Firefox eller Edge, eller hvis du bruker den bærbare versjonen, må du legge til bokmerkedatakatalog og velge riktig nettlesermotor for å få dette programtillegget til å fungere. - For eksempel: Brave's motor er Chromium; og standardplasseringen av bokmerker er: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox-motoren er bokmerkekatalogen brukerdatamappen som inneholder places.sqlite-filen. - Load favicons (can be time consuming during startup) - - + + + + + Nettleserbokmerker + Søk i nettleserbokmerker + + + Failed to set url in clipboard + + + Bokmerkedata + Åpne bokmerker i: + Nytt vindu + Ny fane + Angi nettleser fra banen: + Velg + Kopier nettadresse + Kopier bokmerkets nettadresse til utklippstavlen + Last nettleser fra: + Nettlesernavn + Sti til datakatalog + Legg til + Rediger + Slett + Bla + Andre + Nettlesermotor + Hvis du ikke bruker Chrome, Firefox eller Edge, eller hvis du bruker den bærbare versjonen, må du legge til bokmerkedatakatalog og velge riktig nettlesermotor for å få dette programtillegget til å fungere. + For eksempel: Brave's motor er Chromium; og standardplasseringen av bokmerker er: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox-motoren er bokmerkekatalogen brukerdatamappen som inneholder places.sqlite-filen. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/nl.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nl.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/nl.xaml index c83c2ec7885..1f0b0987a2e 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nl.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/nl.xaml @@ -1,33 +1,33 @@ - - - - - Browser Bookmarks - Search your browser bookmarks - - - Failed to set url in clipboard - - - Bookmark Data - Open bookmarks in: - New window - New tab - Set browser from path: - Choose - Copy url - Copy the bookmark's url to clipboard - Load Browser From: - Browser Name - Data Directory Path - Toevoegen - Bewerken - Verwijder - Browse - Others - Browser Engine - If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. - For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. - Load favicons (can be time consuming during startup) - - + + + + + Browser Bookmarks + Search your browser bookmarks + + + Failed to set url in clipboard + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Toevoegen + Bewerken + Verwijder + Browse + Others + Browser Engine + If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. + For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/pl.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pl.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/pl.xaml index b63c2ffc5c0..940d5626d5f 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pl.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/pl.xaml @@ -1,33 +1,33 @@ - - - - - Zakładki przeglądarki - Przeszukaj zakładki przeglądarki - - - Failed to set url in clipboard - - - Dane zakładek - Otwórz zakładki w: - Nowe okno - Nowa karta - Ustaw przeglądarkę ze ścieżki: - Wybierz - Kopiuj adres url - Skopiuj adres url zakładki do schowka - Załaduj przeglądarkę z: - Nazwa przeglądarki - Ścieżka katalogu danych - Dodaj - Edytuj - Usuń - Przeglądaj - Inne - Silnik przeglądarki - Jeśli nie używasz Chrome, Firefox lub Edge, lub używasz ich wersji przenośnej, musisz dodać katalog danych zakładek i wybrać poprawny silnik przeglądarki, aby wtyczka działała. - Na przykład: silnikiem przeglądarki Brave jest Chromium, a domyślna lokalizacja danych zakładek to: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". W przypadku silnika Firefoksa, katalog zakładek to folder danych użytkownika zawierający plik places.sqlite. - Wczytaj ikony ulubionych (może być czasochłonne podczas uruchamiania) - - + + + + + Zakładki przeglądarki + Przeszukaj zakładki przeglądarki + + + Failed to set url in clipboard + + + Dane zakładek + Otwórz zakładki w: + Nowe okno + Nowa karta + Ustaw przeglądarkę ze ścieżki: + Wybierz + Kopiuj adres url + Skopiuj adres url zakładki do schowka + Załaduj przeglądarkę z: + Nazwa przeglądarki + Ścieżka katalogu danych + Dodaj + Edytuj + Usuń + Przeglądaj + Inne + Silnik przeglądarki + Jeśli nie używasz Chrome, Firefox lub Edge, lub używasz ich wersji przenośnej, musisz dodać katalog danych zakładek i wybrać poprawny silnik przeglądarki, aby wtyczka działała. + Na przykład: silnikiem przeglądarki Brave jest Chromium, a domyślna lokalizacja danych zakładek to: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". W przypadku silnika Firefoksa, katalog zakładek to folder danych użytkownika zawierający plik places.sqlite. + Wczytaj ikony ulubionych (może być czasochłonne podczas uruchamiania) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/pt-br.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-br.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/pt-br.xaml index c6ec5eb4c43..b1a92d76905 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/pt-br.xaml @@ -1,33 +1,33 @@ - - - - - Favoritos do Navegador - Pesquisar favoritos do seu navegador - - - Failed to set url in clipboard - - - Dados de Favoritos - Abrir favoritos em: - Nova janela - Nova aba - Definir navegador pelo caminho: - Escolha - Copiar link - Copiar o link do favorito para a área de transferência - Carregar navegador de: - Nome do Navegador - Caminho do Diretório de Dados - Adicionar - Editar - Apagar - Navegar - Outros - Motor do Navegador - Se você não estiver usando o Chrome, Firefox ou Edge, ou se estiver usando suas versões portáteis, você precisa adicionar o diretório de dados dos favoritos e selecionar a ferramenta de busca correta para fazer este plugin funcionar. - Por exemplo: O motor do Brave é o Chromium; e seu diretório padrão de favoritos é "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Para o motor do Firefox, o diretório de favoritos é a pasta do usuário que contém o arquivo "places.sqlite". - Load favicons (can be time consuming during startup) - - + + + + + Favoritos do Navegador + Pesquisar favoritos do seu navegador + + + Failed to set url in clipboard + + + Dados de Favoritos + Abrir favoritos em: + Nova janela + Nova aba + Definir navegador pelo caminho: + Escolha + Copiar link + Copiar o link do favorito para a área de transferência + Carregar navegador de: + Nome do Navegador + Caminho do Diretório de Dados + Adicionar + Editar + Apagar + Navegar + Outros + Motor do Navegador + Se você não estiver usando o Chrome, Firefox ou Edge, ou se estiver usando suas versões portáteis, você precisa adicionar o diretório de dados dos favoritos e selecionar a ferramenta de busca correta para fazer este plugin funcionar. + Por exemplo: O motor do Brave é o Chromium; e seu diretório padrão de favoritos é "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Para o motor do Firefox, o diretório de favoritos é a pasta do usuário que contém o arquivo "places.sqlite". + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/pt-pt.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-pt.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/pt-pt.xaml index f2b3e93d180..badc2cbf174 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-pt.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/pt-pt.xaml @@ -1,33 +1,33 @@ - - - - - Marcadores do navegador - Pesquisar nos marcadores do navegador - - - Falha ao definir o URL na área de transferência - - - Dados do marcador - Abrir marcadores em: - Nova janela - Novo separador - Caminho do navegador: - Escolher - Copiar URL - Copiar URL do marcador para a área de transferência - Carregar navegador em: - Nome do navegador - Caminho da pasta de dados - Adicionar - Editar - Eliminar - Explorar - Outros - Motor do navegador - Se não estiver a usar o Chrome, Firefox ou Edge ou se estiver a usar a versão portátil, tem que adicionar o diretório de dados dos marcadores e selecionar o motor do navegador para que este plugin funcione. - Por exemplo: o motor do Brave é Chromium e a localização padrão dos marcadores é "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Para o navegador Firefox, o diretório de marcadores é a pasta de utilizador que contém o ficheiro places.sqlite. - Carregar "favicons" (pode atrasar o carregamento da aplicação) - - + + + + + Marcadores do navegador + Pesquisar nos marcadores do navegador + + + Falha ao definir o URL na área de transferência + + + Dados do marcador + Abrir marcadores em: + Nova janela + Novo separador + Caminho do navegador: + Escolher + Copiar URL + Copiar URL do marcador para a área de transferência + Carregar navegador em: + Nome do navegador + Caminho da pasta de dados + Adicionar + Editar + Eliminar + Explorar + Outros + Motor do navegador + Se não estiver a usar o Chrome, Firefox ou Edge ou se estiver a usar a versão portátil, tem que adicionar o diretório de dados dos marcadores e selecionar o motor do navegador para que este plugin funcione. + Por exemplo: o motor do Brave é Chromium e a localização padrão dos marcadores é "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Para o navegador Firefox, o diretório de marcadores é a pasta de utilizador que contém o ficheiro places.sqlite. + Carregar "favicons" (pode atrasar o carregamento da aplicação) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ru.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ru.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ru.xaml index b147c083b94..9d49bf97181 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/ru.xaml @@ -1,33 +1,33 @@ - - - - - Закладки браузера - Поиск закладок в браузере - - - Failed to set url in clipboard - - - Данные закладок - Открыть закладки в: - Новом окне - Новой вкладке - Установить браузер по пути: - Выбор - Скопировать URL-адрес - Скопируйте URL закладки в буфер обмена - Загрузить браузер из: - Название браузера - Путь к каталогу данных - Добавить - Редактировать - Удалить - Обзор - Другое - Движок браузера - Если вы не используете Chrome, Firefox или Edge, или используете их портативную версию, вам необходимо добавить каталог данных закладок и выбрать правильный движок браузера, чтобы этот плагин работал. - Например: Движок Brave - Chromium; и его местоположение данных закладок по умолчанию: «%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData». Для движка Firefox каталог закладок находится в папке userdata и содержит файл places.sqlite. - Load favicons (can be time consuming during startup) - - + + + + + Закладки браузера + Поиск закладок в браузере + + + Failed to set url in clipboard + + + Данные закладок + Открыть закладки в: + Новом окне + Новой вкладке + Установить браузер по пути: + Выбор + Скопировать URL-адрес + Скопируйте URL закладки в буфер обмена + Загрузить браузер из: + Название браузера + Путь к каталогу данных + Добавить + Редактировать + Удалить + Обзор + Другое + Движок браузера + Если вы не используете Chrome, Firefox или Edge, или используете их портативную версию, вам необходимо добавить каталог данных закладок и выбрать правильный движок браузера, чтобы этот плагин работал. + Например: Движок Brave - Chromium; и его местоположение данных закладок по умолчанию: «%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData». Для движка Firefox каталог закладок находится в папке userdata и содержит файл places.sqlite. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/sk.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sk.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/sk.xaml index 1d498a5fda9..f61c53c382d 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sk.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/sk.xaml @@ -1,33 +1,33 @@ - - - - - Záložky prehliadača - Vyhľadáva záložky prehliadača - - - Nepodarilo sa nastaviť URL v schránke - - - Nastavenia pluginu - Otvoriť záložky v: - Nové okno - Nová karta - Nastaviť cestu k prehliadaču: - Prehliadať - Kopírovať URL - Kopírovať URL záložky do schránky - Načítať prehliadač z: - Názov prehliadača - Umiestnenie priečinku s dátami - Pridať - Upraviť - Odstrániť - Prehliadať - Iné - Jadro prehliadača - Ak nepoužívate prehliadač Chrome, Firefox alebo Edge alebo používate ich prenosnú verziu, musíte pridať priečinok s údajmi o záložkách a vybrať správne jadro prehliadača, aby tento plugin fungoval. - Napríklad: Prehliadač Brave má jadro Chromium; predvolené umiestnenie údajov o záložkách je: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Pre jadro Firefoxu je priečinkom záložiek priečinok userdata, ktorý obsahuje súbor places.sqlite. - Načítať favicony (môže trvať dlhšie počas spúšťania) - - + + + + + Záložky prehliadača + Vyhľadáva záložky prehliadača + + + Nepodarilo sa nastaviť URL v schránke + + + Nastavenia pluginu + Otvoriť záložky v: + Nové okno + Nová karta + Nastaviť cestu k prehliadaču: + Prehliadať + Kopírovať URL + Kopírovať URL záložky do schránky + Načítať prehliadač z: + Názov prehliadača + Umiestnenie priečinku s dátami + Pridať + Upraviť + Odstrániť + Prehliadať + Iné + Jadro prehliadača + Ak nepoužívate prehliadač Chrome, Firefox alebo Edge alebo používate ich prenosnú verziu, musíte pridať priečinok s údajmi o záložkách a vybrať správne jadro prehliadača, aby tento plugin fungoval. + Napríklad: Prehliadač Brave má jadro Chromium; predvolené umiestnenie údajov o záložkách je: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Pre jadro Firefoxu je priečinkom záložiek priečinok userdata, ktorý obsahuje súbor places.sqlite. + Načítať favicony (môže trvať dlhšie počas spúšťania) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sr-Cyrl-RS.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/sr-Cyrl-RS.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sr-Cyrl-RS.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/sr-Cyrl-RS.xaml index 02b995517a4..0a6b2f6f899 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sr-Cyrl-RS.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/sr-Cyrl-RS.xaml @@ -1,33 +1,33 @@ - - - - - Browser Bookmarks - Search your browser bookmarks - - - Failed to set url in clipboard - - - Bookmark Data - Open bookmarks in: - New window - New tab - Set browser from path: - Choose - Copy url - Copy the bookmark's url to clipboard - Load Browser From: - Browser Name - Data Directory Path - Add - Edit - Delete - Browse - Others - Browser Engine - If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. - For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. - Load favicons (can be time consuming during startup) - - + + + + + Browser Bookmarks + Search your browser bookmarks + + + Failed to set url in clipboard + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Add + Edit + Delete + Browse + Others + Browser Engine + If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. + For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/sr.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sr.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/sr.xaml index 5a7a18714a6..655b623b612 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sr.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/sr.xaml @@ -1,33 +1,33 @@ - - - - - Browser Bookmarks - Search your browser bookmarks - - - Failed to set url in clipboard - - - Bookmark Data - Open bookmarks in: - New window - New tab - Set browser from path: - Choose - Copy url - Copy the bookmark's url to clipboard - Load Browser From: - Browser Name - Data Directory Path - Dodaj - Izmeni - Obriši - Browse - Others - Browser Engine - If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. - For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. - Load favicons (can be time consuming during startup) - - + + + + + Browser Bookmarks + Search your browser bookmarks + + + Failed to set url in clipboard + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Dodaj + Izmeni + Obriši + Browse + Others + Browser Engine + If you are not using Chrome, Firefox or Edge, or you are using their portable version, you need to add bookmarks data directory and select correct browser engine to make this plugin work. + For example: Brave's engine is Chromium; and its default bookmarks data location is: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". For Firefox engine, the bookmarks directory is the userdata folder contains the places.sqlite file. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/tr.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/tr.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/tr.xaml index 15cd4f250bc..5de84966fe3 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/tr.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/tr.xaml @@ -1,33 +1,33 @@ - - - - - Yer İmleri - Tarayıcınızdaki yer işaretlerini arayın - - - Panoya url ayarlanamadı - - - Yer İmleri Verisi - Yer imlerini şurada aç: - Yeni Pencere - Yeni Sekme - Dizin üzerinden tarayıcı seç: - Seç - Url Kopyala - Yer imi bağlantısını kopyala - Taraycılardan İçe Aktar: - Tarayıcı Adı - Veri Dizini - Ekle - Düzenle - Sil - Gözat - Diğerleri - Tarayıcı Motoru - Eğer Chrome, Firefox veya Edge kullanmıyor veya bu tarayıcıların taşınabilir sürümlerini kullanıyorsanız tarayıcı motorunu ve yer imlerinin saklandığı dizini elle girmeniz gerekir. - Örneğin: Brave tarayıcısı Chromium tabanlıdır ve yer imleri varsayılan olarak "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData" klasöründe saklanır. Firefox tabanlı tarayıcılar için bu places.sqlite dosyasının bulunduğu kullanıcı verisi klasörüdür. - Site simgelerini yükle (başlangıç sırasında zaman alabilir) - - + + + + + Yer İmleri + Tarayıcınızdaki yer işaretlerini arayın + + + Panoya url ayarlanamadı + + + Yer İmleri Verisi + Yer imlerini şurada aç: + Yeni Pencere + Yeni Sekme + Dizin üzerinden tarayıcı seç: + Seç + Url Kopyala + Yer imi bağlantısını kopyala + Taraycılardan İçe Aktar: + Tarayıcı Adı + Veri Dizini + Ekle + Düzenle + Sil + Gözat + Diğerleri + Tarayıcı Motoru + Eğer Chrome, Firefox veya Edge kullanmıyor veya bu tarayıcıların taşınabilir sürümlerini kullanıyorsanız tarayıcı motorunu ve yer imlerinin saklandığı dizini elle girmeniz gerekir. + Örneğin: Brave tarayıcısı Chromium tabanlıdır ve yer imleri varsayılan olarak "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData" klasöründe saklanır. Firefox tabanlı tarayıcılar için bu places.sqlite dosyasının bulunduğu kullanıcı verisi klasörüdür. + Site simgelerini yükle (başlangıç sırasında zaman alabilir) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/uk-UA.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/uk-UA.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/uk-UA.xaml index a40370a9c69..00de5f2d3b8 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/uk-UA.xaml @@ -1,33 +1,33 @@ - - - - - Закладки браузера - Пошук у закладках браузера - - - Не вдалося вставити Url-адресу в буфер обміну - - - Дані закладок - Відкрити закладки в: - Нове вікно - Нова вкладка - Встановити браузер за шляхом: - Обрати - Скопіювати URL-адресу - Скопіюйте URL-адресу закладки в буфер обміну - Завантажити браузер з: - Назва браузера - Шлях до каталогу з даними - Додати - Редагувати - Видалити - Перегляд - Інші - Браузерний рушій - Якщо ви не використовуєте Chrome, Firefox або Edge, або використовуєте їхні портативні версії, вам потрібно додати каталог даних закладок і вибрати правильний рушій браузера, щоб цей плагін працював. - Наприклад: рушій Brave — Chromium, і типово розташування даних закладок: «%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData». Для браузера Firefox директорія закладок — це тека userdata, що містить файл places.sqlite. - Завантажити піктограми (може зайняти багато часу під час запуску) - - + + + + + Закладки браузера + Пошук у закладках браузера + + + Не вдалося вставити Url-адресу в буфер обміну + + + Дані закладок + Відкрити закладки в: + Нове вікно + Нова вкладка + Встановити браузер за шляхом: + Обрати + Скопіювати URL-адресу + Скопіюйте URL-адресу закладки в буфер обміну + Завантажити браузер з: + Назва браузера + Шлях до каталогу з даними + Додати + Редагувати + Видалити + Перегляд + Інші + Браузерний рушій + Якщо ви не використовуєте Chrome, Firefox або Edge, або використовуєте їхні портативні версії, вам потрібно додати каталог даних закладок і вибрати правильний рушій браузера, щоб цей плагін працював. + Наприклад: рушій Brave — Chromium, і типово розташування даних закладок: «%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData». Для браузера Firefox директорія закладок — це тека userdata, що містить файл places.sqlite. + Завантажити піктограми (може зайняти багато часу під час запуску) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/vi.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/vi.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/vi.xaml index f52a7fe7b73..deb0a702786 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/vi.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/vi.xaml @@ -1,33 +1,33 @@ - - - - - Dấu trang trình duyệt - Tìm kiếm dấu trang trình duyệt của bạn - - - Failed to set url in clipboard - - - Dữ liệu đánh dấu - Mở dấu trang trong: - Cửa sổ mới - Thêm Tab mới - Đặt trình duyệt từ đường dẫn: - Chọn - Sao chép url - Sao chép url của dấu trang vào clipboard - Tải trình duyệt từ: - Tên trình duyệt - Đường dẫn thư mục dữ liệu - Thêm - Sửa - Xóa - Duyệt - Khác - Công cụ trình duyệt - Nếu bạn không sử dụng Chrome, Firefox hoặc Edge hoặc bạn đang sử dụng phiên bản di động của chúng, bạn cần thêm thư mục dữ liệu dấu trang và chọn đúng công cụ trình duyệt để plugin này hoạt động. - Ví dụ: Engine của Brave là Chrome; và vị trí dữ liệu dấu trang mặc định của nó là: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Đối với công cụ Firefox, thư mục dấu trang là thư mục dữ liệu người dùng chứa tệp địa điểm.sqlite. - Load favicons (can be time consuming during startup) - - + + + + + Dấu trang trình duyệt + Tìm kiếm dấu trang trình duyệt của bạn + + + Failed to set url in clipboard + + + Dữ liệu đánh dấu + Mở dấu trang trong: + Cửa sổ mới + Thêm Tab mới + Đặt trình duyệt từ đường dẫn: + Chọn + Sao chép url + Sao chép url của dấu trang vào clipboard + Tải trình duyệt từ: + Tên trình duyệt + Đường dẫn thư mục dữ liệu + Thêm + Sửa + Xóa + Duyệt + Khác + Công cụ trình duyệt + Nếu bạn không sử dụng Chrome, Firefox hoặc Edge hoặc bạn đang sử dụng phiên bản di động của chúng, bạn cần thêm thư mục dữ liệu dấu trang và chọn đúng công cụ trình duyệt để plugin này hoạt động. + Ví dụ: Engine của Brave là Chrome; và vị trí dữ liệu dấu trang mặc định của nó là: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Đối với công cụ Firefox, thư mục dấu trang là thư mục dữ liệu người dùng chứa tệp địa điểm.sqlite. + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/zh-cn.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-cn.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/zh-cn.xaml index 778cd399211..150bf34bf0f 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/zh-cn.xaml @@ -1,33 +1,33 @@ - - - - - 浏览器书签 - 搜索您的浏览器书签 - - - 无法复制 URL 到剪切板 - - - 书签数据 - 在以下位置打开书签: - 新窗口 - 新标签 - 设置浏览器路径: - 选择 - 复制网址 - 将书签的网址复制到剪贴板 - 从以下浏览器加载: - 浏览器名称 - 数据文件路径 - 添加 - 编辑 - 删除 - 浏览 - 其他 - 浏览器引擎 - 如果你没有使用 Chrome、 Firefox 或 Edge,或者正在使用它们的绿色版, 那么你需要添加书签数据目录并选择正确的浏览器引擎才能使此插件正常工作。 - 例如:Brave 浏览器的引擎是 Chromium;其默认书签数据位置是 "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData"。 对于 Firefox 引擎的浏览器,书签目录是 places.sqlite 文件所在的用户数据文件夹。 - 载入收藏夹图标 (启动时可能十分耗时) - - + + + + + 浏览器书签 + 搜索您的浏览器书签 + + + 无法复制 URL 到剪切板 + + + 书签数据 + 在以下位置打开书签: + 新窗口 + 新标签 + 设置浏览器路径: + 选择 + 复制网址 + 将书签的网址复制到剪贴板 + 从以下浏览器加载: + 浏览器名称 + 数据文件路径 + 添加 + 编辑 + 删除 + 浏览 + 其他 + 浏览器引擎 + 如果你没有使用 Chrome、 Firefox 或 Edge,或者正在使用它们的绿色版, 那么你需要添加书签数据目录并选择正确的浏览器引擎才能使此插件正常工作。 + 例如:Brave 浏览器的引擎是 Chromium;其默认书签数据位置是 "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData"。 对于 Firefox 引擎的浏览器,书签目录是 places.sqlite 文件所在的用户数据文件夹。 + 载入收藏夹图标 (启动时可能十分耗时) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/zh-tw.xaml similarity index 99% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-tw.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/zh-tw.xaml index e8bc6bcb668..7c221dafe47 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-tw.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Languages/zh-tw.xaml @@ -1,33 +1,33 @@ - - - - - 瀏覽器書籤 - 搜尋你的瀏覽器書籤 - - - Failed to set url in clipboard - - - 書籤資料 - 載入書籤至: - 新增視窗 - 新增分頁 - 設定瀏覽器路徑: - 選擇 - 複製網址 - 複製書籤網址 - 載入瀏覽器自: - 瀏覽器名稱 - 檔案目錄路徑 - 新增 - 編輯 - 刪除 - 瀏覽 - 其他 - 瀏覽器引擎 - 如果你沒有使用 Chrome、Firefox 或 Edge,或者使用它們的便攜版,你需要新增書籤資料位置並選擇正確的瀏覽器引擎,才能讓這個擴充功能正常運作。 - 例如:Brave 瀏覽器的引擎是 Chromium;而它的預設書籤資料位置是「%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData」。對於 Firefox 瀏覽器引擎,書籤資料位置是包含 places.sqlite 檔案的 userdata 資料夾。 - Load favicons (can be time consuming during startup) - - + + + + + 瀏覽器書籤 + 搜尋你的瀏覽器書籤 + + + Failed to set url in clipboard + + + 書籤資料 + 載入書籤至: + 新增視窗 + 新增分頁 + 設定瀏覽器路徑: + 選擇 + 複製網址 + 複製書籤網址 + 載入瀏覽器自: + 瀏覽器名稱 + 檔案目錄路徑 + 新增 + 編輯 + 刪除 + 瀏覽 + 其他 + 瀏覽器引擎 + 如果你沒有使用 Chrome、Firefox 或 Edge,或者使用它們的便攜版,你需要新增書籤資料位置並選擇正確的瀏覽器引擎,才能讓這個擴充功能正常運作。 + 例如:Brave 瀏覽器的引擎是 Chromium;而它的預設書籤資料位置是「%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData」。對於 Firefox 瀏覽器引擎,書籤資料位置是包含 places.sqlite 檔案的 userdata 資料夾。 + Load favicons (can be time consuming during startup) + + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Main.cs new file mode 100644 index 00000000000..6bb89bbb156 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Main.cs @@ -0,0 +1,267 @@ +#nullable enable +using Flow.Launcher.Plugin.BrowserBookmarks.Models; +using System; +using System.Collections.Generic; +using System.Windows.Controls; +using System.Threading; +using System.Threading.Tasks; +using Flow.Launcher.Plugin.BrowserBookmarks.Services; +using System.ComponentModel; +using System.Linq; +using Flow.Launcher.Plugin.SharedCommands; +using System.Collections.Specialized; +using Flow.Launcher.Plugin.SharedModels; +using System.IO; +using System.Collections.Concurrent; + +namespace Flow.Launcher.Plugin.BrowserBookmarks; + +public class Main : ISettingProvider, IPlugin, IAsyncReloadable, IPluginI18n, IContextMenu, IDisposable +{ + internal static PluginInitContext Context { get; set; } = null!; + private static Settings _settings = null!; + + private BookmarkLoaderService _bookmarkLoader = null!; + private FaviconService _faviconService = null!; + private BookmarkWatcherService _bookmarkWatcher = null!; + + private List _bookmarks = new(); + private readonly CancellationTokenSource _cancellationTokenSource = new(); + private PeriodicTimer? _firefoxBookmarkTimer; + private static readonly TimeSpan FirefoxPollingInterval = TimeSpan.FromHours(3); + + public void Init(PluginInitContext context) + { + Context = context; + _settings = context.API.LoadSettingJsonStorage(); + _settings.PropertyChanged += OnSettingsPropertyChanged; + _settings.CustomBrowsers.CollectionChanged += OnCustomBrowsersChanged; + + var tempPath = SetupTempDirectory(); + + _bookmarkLoader = new BookmarkLoaderService(Context, _settings, tempPath); + _faviconService = new FaviconService(Context, _settings, tempPath); + _bookmarkWatcher = new BookmarkWatcherService(); + _bookmarkWatcher.OnBookmarkFileChanged += OnBookmarkFileChanged; + + // Fire and forget the initial load to make Flow's UI responsive immediately. + _ = ReloadDataAsync(); + StartFirefoxBookmarkTimer(); + } + + private string SetupTempDirectory() + { + var tempPath = Path.Combine(Context.CurrentPluginMetadata.PluginCacheDirectoryPath, "Temp"); + try + { + if (Directory.Exists(tempPath)) + { + Directory.Delete(tempPath, true); + } + Directory.CreateDirectory(tempPath); + } + catch (Exception e) + { + Context.API.LogException(nameof(Main), "Failed to set up temporary directory.", e); + } + return tempPath; + } + + public List Query(Query query) + { + var search = query.Search.Trim(); + var bookmarks = _bookmarks; // use a local copy + + if (!string.IsNullOrEmpty(search)) + { + return bookmarks + .Select(b => + { + var match = Context.API.FuzzySearch(search, b.Name); + if(!match.IsSearchPrecisionScoreMet()) + match = Context.API.FuzzySearch(search, b.Url); + return (b, match); + }) + .Where(t => t.match.IsSearchPrecisionScoreMet()) + .OrderByDescending(t => t.match.Score) + .Select(t => CreateResult(t.b, t.match.Score)) + .ToList(); + } + + return bookmarks.Select(b => CreateResult(b, 0)).ToList(); + } + + private Result CreateResult(Bookmark bookmark, int score) => new() + { + Title = bookmark.Name, + SubTitle = bookmark.Url, + IcoPath = !string.IsNullOrEmpty(bookmark.FaviconPath) + ? bookmark.FaviconPath + : @"Images\bookmark.png", + Score = score, + Action = _ => + { + Context.API.OpenUrl(bookmark.Url); + return true; + }, + ContextData = bookmark.Url + }; + + public async Task ReloadDataAsync() + { + var bookmarks = await _bookmarkLoader.LoadBookmarksAsync(_cancellationTokenSource.Token); + + // Atomically swap the list. This prevents the Query method from seeing a partially loaded list. + Volatile.Write(ref _bookmarks, bookmarks); + + _bookmarkWatcher.UpdateWatchers(_bookmarkLoader.DiscoveredBookmarkFiles); + + // Fire and forget favicon processing to not block the UI + _ = _faviconService.ProcessBookmarkFavicons(_bookmarks, _cancellationTokenSource.Token); + } + + private void OnSettingsPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName is nameof(Settings.LoadFirefoxBookmark)) + { + StartFirefoxBookmarkTimer(); + } + _ = ReloadDataAsync(); + } + + private void OnCustomBrowsersChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + StartFirefoxBookmarkTimer(); + _ = ReloadDataAsync(); + } + + private void OnBookmarkFileChanged() + { + _ = ReloadDataAsync(); + } + + private void StartFirefoxBookmarkTimer() + { + _firefoxBookmarkTimer?.Dispose(); + + if (!_settings.LoadFirefoxBookmark && !_settings.CustomBrowsers.Any(x => x.BrowserType == BrowserType.Firefox)) + return; + + _firefoxBookmarkTimer = new PeriodicTimer(FirefoxPollingInterval); + + _ = Task.Run(async () => + { + while (await _firefoxBookmarkTimer.WaitForNextTickAsync(_cancellationTokenSource.Token)) + { + await ReloadFirefoxBookmarksAsync(); + } + }, _cancellationTokenSource.Token); + } + + private async Task ReloadFirefoxBookmarksAsync() + { + Context.API.LogInfo(nameof(Main), "Starting periodic reload of Firefox bookmarks."); + + var firefoxLoaders = _bookmarkLoader.GetFirefoxBookmarkLoaders().ToList(); + if (!firefoxLoaders.Any()) + { + Context.API.LogInfo(nameof(Main), "No Firefox bookmark loaders enabled, skipping reload."); + return; + } + + var firefoxBookmarks = new ConcurrentBag(); + var tasks = firefoxLoaders.Select(async loader => + { + try + { + await foreach (var bookmark in loader.GetBookmarksAsync(_cancellationTokenSource.Token)) + { + firefoxBookmarks.Add(bookmark); + } + } + catch (OperationCanceledException) { } // Task was cancelled, swallow exception + catch (Exception e) + { + Context.API.LogException(nameof(Main), $"Failed to load bookmarks from {loader.Name}.", e); + } + }); + + await Task.WhenAll(tasks); + + if (firefoxBookmarks.IsEmpty) + { + Context.API.LogInfo(nameof(Main), "No Firefox bookmarks found during periodic reload."); + return; + } + + var currentBookmarks = Volatile.Read(ref _bookmarks); + + var firefoxLoaderNames = firefoxLoaders.Select(l => l.Name).ToHashSet(); + var otherBookmarks = currentBookmarks.Where(b => !firefoxLoaderNames.Any(name => b.Source.StartsWith(name, StringComparison.OrdinalIgnoreCase))); + + var newBookmarkList = otherBookmarks.Concat(firefoxBookmarks).Distinct().ToList(); + + Volatile.Write(ref _bookmarks, newBookmarkList); + + Context.API.LogInfo(nameof(Main), $"Periodic reload complete. Loaded {firefoxBookmarks.Count} Firefox bookmarks."); + + _ = _faviconService.ProcessBookmarkFavicons(firefoxBookmarks.ToList(), _cancellationTokenSource.Token); + } + + public Control CreateSettingPanel() + { + return new Views.SettingsControl(_settings); + } + + public string GetTranslatedPluginTitle() + { + return Localize.flowlauncher_plugin_browserbookmark_plugin_name(); + } + + public string GetTranslatedPluginDescription() + { + return Localize.flowlauncher_plugin_browserbookmark_plugin_description(); + } + + public List LoadContextMenus(Result selectedResult) + { + if (selectedResult.ContextData is not string url) + return new List(); + + return new List + { + new() + { + Title = Localize.flowlauncher_plugin_browserbookmark_copyurl_title(), + SubTitle = Localize.flowlauncher_plugin_browserbookmark_copyurl_subtitle(), + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8"), + IcoPath = @"Images\copylink.png", + Action = _ => + { + try + { + Context.API.CopyToClipboard(url); + return true; + } + catch(Exception ex) + { + Context.API.LogException(nameof(Main), "Failed to copy URL to clipboard", ex); + Context.API.ShowMsgError(Localize.flowlauncher_plugin_browserbookmark_copy_failed()); + return false; + } + } + } + }; + } + + public void Dispose() + { + _settings.PropertyChanged -= OnSettingsPropertyChanged; + _settings.CustomBrowsers.CollectionChanged -= OnCustomBrowsersChanged; + _firefoxBookmarkTimer?.Dispose(); + _cancellationTokenSource.Cancel(); + _cancellationTokenSource.Dispose(); + _faviconService.Dispose(); + _bookmarkWatcher.Dispose(); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/BaseModel.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/BaseModel.cs new file mode 100644 index 00000000000..8fbd37548c7 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/BaseModel.cs @@ -0,0 +1,16 @@ +#nullable enable +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Models; + +// A base class that implements INotifyPropertyChanged for view models. +public abstract class BaseModel : INotifyPropertyChanged +{ + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/Bookmark.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/Bookmark.cs new file mode 100644 index 00000000000..0bcb802967f --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/Bookmark.cs @@ -0,0 +1,19 @@ +#nullable enable +using System; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Models; + +public record Bookmark(string Name, string Url, string Source, string ProfilePath) +{ + public override int GetHashCode() + { + return HashCode.Combine(Name, Url); + } + + public virtual bool Equals(Bookmark? other) + { + return other is not null && Name == other.Name && Url == other.Url; + } + + public string FaviconPath { get; set; } = string.Empty; +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/CustomBrowser.cs similarity index 74% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/CustomBrowser.cs index af1e3fee496..d1772bc10f2 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/CustomBrowser.cs @@ -1,13 +1,14 @@ -using System.Collections.Generic; using Flow.Launcher.Localization.Attributes; +using System.Collections.Generic; +using System.Linq; -namespace Flow.Launcher.Plugin.BrowserBookmark.Models; +namespace Flow.Launcher.Plugin.BrowserBookmarks.Models; public class CustomBrowser : BaseModel { private string _name; private string _dataDirectoryPath; - private BrowserType _browserType = BrowserType.Chromium; + private BrowserType _browserType = BrowserType.Unknown; public string Name { @@ -35,8 +36,6 @@ public string DataDirectoryPath } } - public List AllBrowserTypes { get; } = BrowserTypeLocalized.GetValues(); - public BrowserType BrowserType { get => _browserType; @@ -51,9 +50,15 @@ public BrowserType BrowserType } } +// Helper record for displaying enum values in the settings ComboBox. +public record BrowserTypeDisplay(string Display, BrowserType Value); + [EnumLocalize] public enum BrowserType { + [EnumLocalizeValue("Unknown")] + Unknown, + [EnumLocalizeValue("Chromium")] Chromium, diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/Settings.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/Settings.cs new file mode 100644 index 00000000000..998a16659bd --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Models/Settings.cs @@ -0,0 +1,51 @@ +using System.Collections.ObjectModel; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Models; + +public class Settings : BaseModel +{ + private bool _loadChromeBookmark = true; + private bool _loadFirefoxBookmark = true; + private bool _loadEdgeBookmark = true; + private bool _loadChromiumBookmark = true; + private bool _enableFavicons = true; + private bool _fetchMissingFavicons = false; + + public bool LoadChromeBookmark + { + get => _loadChromeBookmark; + set { _loadChromeBookmark = value; OnPropertyChanged(); } + } + + public bool LoadFirefoxBookmark + { + get => _loadFirefoxBookmark; + set { _loadFirefoxBookmark = value; OnPropertyChanged(); } + } + + public bool LoadEdgeBookmark + { + get => _loadEdgeBookmark; + set { _loadEdgeBookmark = value; OnPropertyChanged(); } + } + + public bool LoadChromiumBookmark + { + get => _loadChromiumBookmark; + set { _loadChromiumBookmark = value; OnPropertyChanged(); } + } + + public bool EnableFavicons + { + get => _enableFavicons; + set { _enableFavicons = value; OnPropertyChanged(); } + } + + public bool FetchMissingFavicons + { + get => _fetchMissingFavicons; + set { _fetchMissingFavicons = value; OnPropertyChanged(); } + } + + public ObservableCollection CustomBrowsers { get; set; } = new(); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/BookmarkLoaderService.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/BookmarkLoaderService.cs new file mode 100644 index 00000000000..9d439bccb3c --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/BookmarkLoaderService.cs @@ -0,0 +1,156 @@ +#nullable enable +using Flow.Launcher.Plugin.BrowserBookmarks.Models; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Services; + +public class BookmarkLoaderService +{ + private readonly PluginInitContext _context; + private readonly Settings _settings; + private readonly string _tempPath; + + // This will hold the actual paths to the bookmark files for the watcher service + public List DiscoveredBookmarkFiles { get; } = new(); + + public BookmarkLoaderService(PluginInitContext context, Settings settings, string tempPath) + { + _context = context; + _settings = settings; + _tempPath = tempPath; + } + + public async Task> LoadBookmarksAsync(CancellationToken cancellationToken) + { + DiscoveredBookmarkFiles.Clear(); + var bookmarks = new ConcurrentBag(); + var loaders = GetBookmarkLoaders(); + + var tasks = loaders.Select(async loader => + { + try + { + await foreach (var bookmark in loader.GetBookmarksAsync(cancellationToken).WithCancellation(cancellationToken)) + { + bookmarks.Add(bookmark); + } + } + catch (OperationCanceledException) + { + // Task was cancelled, swallow exception + } + catch (Exception e) + { + _context.API.LogException(nameof(BookmarkLoaderService), $"Failed to load bookmarks from {loader.Name}.", e); + } + }); + + await Task.WhenAll(tasks); + + return bookmarks.Distinct().ToList(); + } + + public IEnumerable GetBookmarkLoaders() + { + return GetChromiumBookmarkLoaders().Concat(GetFirefoxBookmarkLoaders()); + } + + public IEnumerable GetChromiumBookmarkLoaders() + { + var logAction = (string tag, string msg, Exception? ex) => _context.API.LogException(tag, msg, ex); + + if (_settings.LoadChromeBookmark) + { + var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Google\Chrome\User Data"); + if (Directory.Exists(path)) + yield return new ChromiumBookmarkLoader("Google Chrome", path, logAction, DiscoveredBookmarkFiles); + } + + if (_settings.LoadEdgeBookmark) + { + var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\Edge\User Data"); + if (Directory.Exists(path)) + yield return new ChromiumBookmarkLoader("Microsoft Edge", path, logAction, DiscoveredBookmarkFiles); + } + + if (_settings.LoadChromiumBookmark) + { + var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Chromium\User Data"); + if (Directory.Exists(path)) + yield return new ChromiumBookmarkLoader("Chromium", path, logAction, DiscoveredBookmarkFiles); + } + + foreach (var browser in _settings.CustomBrowsers.Where(b => b.BrowserType == BrowserType.Chromium)) + { + if (string.IsNullOrEmpty(browser.Name) || string.IsNullOrEmpty(browser.DataDirectoryPath) || !Directory.Exists(browser.DataDirectoryPath)) + continue; + + yield return new ChromiumBookmarkLoader(browser.Name, browser.DataDirectoryPath, logAction, DiscoveredBookmarkFiles); + } + } + + public IEnumerable GetFirefoxBookmarkLoaders() + { + var logAction = (string tag, string msg, Exception? ex) => _context.API.LogException(tag, msg, ex); + + if (_settings.LoadFirefoxBookmark) + { + string? placesPath = null; + try + { + placesPath = FirefoxProfileFinder.GetFirefoxPlacesPath(); + } + catch (Exception ex) + { + _context.API.LogException(nameof(BookmarkLoaderService), "Failed to find Firefox profile", ex); + } + if (!string.IsNullOrEmpty(placesPath)) + { + yield return new FirefoxBookmarkLoader("Firefox", placesPath, _tempPath, logAction); + } + + string? msixPlacesPath = null; + try + { + msixPlacesPath = FirefoxProfileFinder.GetFirefoxMsixPlacesPath(); + } + catch (Exception ex) + { + _context.API.LogException(nameof(BookmarkLoaderService), "Failed to find Firefox MSIX package", ex); + } + if (!string.IsNullOrEmpty(msixPlacesPath)) + { + yield return new FirefoxBookmarkLoader("Firefox (Store)", msixPlacesPath, _tempPath, logAction); + } + } + + foreach (var browser in _settings.CustomBrowsers.Where(b => b.BrowserType == BrowserType.Firefox)) + { + if (string.IsNullOrEmpty(browser.Name) || string.IsNullOrEmpty(browser.DataDirectoryPath) || !Directory.Exists(browser.DataDirectoryPath)) + continue; + + yield return CreateCustomFirefoxLoader(browser.Name, browser.DataDirectoryPath); + } + } + + private IBookmarkLoader CreateCustomFirefoxLoader(string name, string dataDirectoryPath) + { + var logAction = (string tag, string msg, Exception? ex) => _context.API.LogException(tag, msg, ex); + // Custom Firefox paths might point to the root profile dir (e.g. ...\Mozilla\Firefox) + var placesPath = FirefoxProfileFinder.GetPlacesPathFromProfileDir(dataDirectoryPath); + if (string.IsNullOrEmpty(placesPath)) + { + // Or they might point directly to a profile folder (e.g. ...\Profiles\xyz.default-release) + placesPath = Path.Combine(dataDirectoryPath, "places.sqlite"); + } + + // Do not add Firefox places.sqlite to the watcher as it's updated constantly for history. + return new FirefoxBookmarkLoader(name, placesPath, _tempPath, logAction); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/BookmarkWatcherService.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/BookmarkWatcherService.cs new file mode 100644 index 00000000000..5b7f4dbcce1 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/BookmarkWatcherService.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Services; + +public class BookmarkWatcherService : IDisposable +{ + private readonly List _watchers = new(); + public event Action OnBookmarkFileChanged; + + // Timer to debounce file change events + private Timer _debounceTimer; + private readonly object _lock = new(); + + public BookmarkWatcherService() + { + _debounceTimer = new Timer(_ => OnBookmarkFileChanged?.Invoke(), null, Timeout.Infinite, Timeout.Infinite); + } + + public void UpdateWatchers(IEnumerable filePaths) + { + // Dispose old watchers + foreach (var watcher in _watchers) + { + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + } + _watchers.Clear(); + + // Create a new, specific watcher for each individual bookmark file. + foreach (var filePath in filePaths) + { + var directory = Path.GetDirectoryName(filePath); + var fileName = Path.GetFileName(filePath); + + if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(fileName) || !Directory.Exists(directory)) + continue; + + var watcher = new FileSystemWatcher(directory) + { + Filter = fileName, + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Size + }; + watcher.Changed += OnFileChanged; + watcher.Created += OnFileChanged; + watcher.Deleted += OnFileChanged; + watcher.Renamed += OnFileRenamed; + watcher.EnableRaisingEvents = true; + _watchers.Add(watcher); + } + } + + private void OnFileChanged(object sender, FileSystemEventArgs e) + { + TriggerDebouncedReload(); + } + + private void OnFileRenamed(object sender, RenamedEventArgs e) + { + TriggerDebouncedReload(); + } + + private void TriggerDebouncedReload() + { + // Reset the timer to fire after 2 seconds. + // This prevents multiple reloads if a browser writes to the file several times in quick succession. + lock (_lock) + { + _debounceTimer.Change(2000, Timeout.Infinite); + } + } + + public void Dispose() + { + _debounceTimer?.Dispose(); + foreach (var watcher in _watchers) + { + watcher.EnableRaisingEvents = false; + watcher.Dispose(); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/BrowserDetector.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/BrowserDetector.cs new file mode 100644 index 00000000000..1a9d354c9c5 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/BrowserDetector.cs @@ -0,0 +1,39 @@ +#nullable enable +using Flow.Launcher.Plugin.BrowserBookmarks.Models; +using System.IO; +using System.Linq; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Services; + +public static class BrowserDetector +{ + public static BrowserType DetectBrowserType(string dataDirectoryPath) + { + if (string.IsNullOrEmpty(dataDirectoryPath) || !Directory.Exists(dataDirectoryPath)) + return BrowserType.Unknown; + + // Check for Chromium-based browsers by looking for the 'Bookmarks' file. + // This includes checking common profile subdirectories. + var profileDirectories = Directory.EnumerateDirectories(dataDirectoryPath, "Profile *").ToList(); + var defaultProfile = Path.Combine(dataDirectoryPath, "Default"); + if (Directory.Exists(defaultProfile)) + profileDirectories.Add(defaultProfile); + + // Also check the root directory itself, as some browsers use it directly. + profileDirectories.Add(dataDirectoryPath); + + if (profileDirectories.Any(p => File.Exists(Path.Combine(p, "Bookmarks")))) + { + return BrowserType.Chromium; + } + + // Check for Firefox-based browsers by looking for 'places.sqlite'. + // This leverages the existing FirefoxProfileFinder logic. + if (File.Exists(Path.Combine(dataDirectoryPath, "places.sqlite")) || !string.IsNullOrEmpty(FirefoxProfileFinder.GetPlacesPathFromProfileDir(dataDirectoryPath))) + { + return BrowserType.Firefox; + } + + return BrowserType.Unknown; + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/ChromiumBookmarkLoader.cs new file mode 100644 index 00000000000..d88787ba905 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/ChromiumBookmarkLoader.cs @@ -0,0 +1,116 @@ +#nullable enable +using Flow.Launcher.Plugin.BrowserBookmarks.Models; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text.Json; +using System.Threading; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Services; + +public class ChromiumBookmarkLoader : IBookmarkLoader +{ + private readonly string _browserName; + private readonly string _browserDataPath; + private readonly Action _logException; + private readonly List _discoveredFiles; + + public string Name => _browserName; + + public ChromiumBookmarkLoader(string browserName, string browserDataPath, Action logException, List discoveredFiles) + { + _browserName = browserName; + _browserDataPath = browserDataPath; + _logException = logException; + _discoveredFiles = discoveredFiles; + } + + public async IAsyncEnumerable GetBookmarksAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (!Directory.Exists(_browserDataPath)) + yield break; + + var profileDirectories = Directory.EnumerateDirectories(_browserDataPath, "Profile *").ToList(); + var defaultProfile = Path.Combine(_browserDataPath, "Default"); + if(Directory.Exists(defaultProfile)) + profileDirectories.Add(defaultProfile); + + foreach (var profilePath in profileDirectories) + { + cancellationToken.ThrowIfCancellationRequested(); + var bookmarkPath = Path.Combine(profilePath, "Bookmarks"); + if (!File.Exists(bookmarkPath)) + continue; + + _discoveredFiles.Add(bookmarkPath); + var source = _browserName + (Path.GetFileName(profilePath) == "Default" ? "" : $" ({Path.GetFileName(profilePath)})"); + + var bookmarks = new List(); + try + { + await using var stream = File.OpenRead(bookmarkPath); + using var jsonDocument = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken); + + if (jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) + { + bookmarks.AddRange(EnumerateBookmarks(rootElement, source, profilePath)); + } + } + catch(JsonException ex) + { + _logException(nameof(ChromiumBookmarkLoader), $"Failed to parse bookmarks file for {_browserName}: {bookmarkPath}", ex); + } + + foreach (var bookmark in bookmarks) + { + yield return bookmark; + } + } + } + + private IEnumerable EnumerateBookmarks(JsonElement rootElement, string source, string profilePath) + { + var bookmarks = new List(); + foreach (var folder in rootElement.EnumerateObject()) + { + if (folder.Value.ValueKind == JsonValueKind.Object) + EnumerateFolderBookmark(folder.Value, bookmarks, source, profilePath); + } + return bookmarks; + } + + private void EnumerateFolderBookmark(JsonElement folderElement, ICollection bookmarks, string source, string profilePath) + { + if (!folderElement.TryGetProperty("children", out var childrenElement)) + return; + + foreach (var subElement in childrenElement.EnumerateArray()) + { + if (subElement.TryGetProperty("type", out var type)) + { + switch (type.GetString()) + { + case "folder": + case "workspace": // Edge Workspace + EnumerateFolderBookmark(subElement, bookmarks, source, profilePath); + break; + case "url": + if (subElement.TryGetProperty("name", out var name) && + subElement.TryGetProperty("url", out var url) && + !string.IsNullOrEmpty(name.GetString()) && + !string.IsNullOrEmpty(url.GetString())) + { + bookmarks.Add(new Bookmark(name.GetString()!, url.GetString()!, source, profilePath)); + } + break; + } + } + else + { + _logException(nameof(ChromiumBookmarkLoader), $"type property not found for {subElement.GetString()}", null); + } + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/FaviconService.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/FaviconService.cs new file mode 100644 index 00000000000..8673be2d006 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/FaviconService.cs @@ -0,0 +1,426 @@ +#nullable enable +using Flow.Launcher.Plugin.BrowserBookmarks.Models; +using SkiaSharp; +using Svg.Skia; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Services; + +public partial class FaviconService : IDisposable +{ + private readonly PluginInitContext _context; + private readonly Settings _settings; + private readonly string _faviconCacheDir; + private readonly HttpClient _httpClient; + private readonly LocalFaviconExtractor _localExtractor; + + private readonly ConcurrentDictionary> _ongoingFetches = new(); + + [GeneratedRegex("]+>", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] + private static partial Regex LinkTagRegex(); + [GeneratedRegex("rel\\s*=\\s*(?:['\"](?[^'\"]*)['\"]|(?[^>\\s]+))", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] + private static partial Regex RelAttributeRegex(); + [GeneratedRegex("href\\s*=\\s*(?:['\"](?[^'\"]*)['\"]|(?[^>\\s]+))", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] + private static partial Regex HrefAttributeRegex(); + [GeneratedRegex("sizes\\s*=\\s*(?:['\"](?[^'\"]*)['\"]|(?[^>\\s]+))", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] + private static partial Regex SizesAttributeRegex(); + [GeneratedRegex("]+href\\s*=\\s*(?:['\"](?[^'\"]*)['\"]|(?[^>\\s]+))", RegexOptions.IgnoreCase | RegexOptions.Compiled, "en-US")] + private static partial Regex BaseHrefRegex(); + + private record struct FaviconCandidate(string Url, int Score); + private record struct FetchResult(string? TempPath, int Size); + + public FaviconService(PluginInitContext context, Settings settings, string tempPath) + { + _context = context; + _settings = settings; + + _faviconCacheDir = Path.Combine(context.CurrentPluginMetadata.PluginCacheDirectoryPath, "FaviconCache"); + Directory.CreateDirectory(_faviconCacheDir); + + _localExtractor = new LocalFaviconExtractor(context, tempPath); + + var handler = new HttpClientHandler { AllowAutoRedirect = true }; + _httpClient = new HttpClient(handler); + _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"); + _httpClient.DefaultRequestHeaders.Accept.ParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"); + _httpClient.Timeout = TimeSpan.FromSeconds(5); + } + + public async Task ProcessBookmarkFavicons(IReadOnlyList bookmarks, CancellationToken cancellationToken) + { + if (!_settings.EnableFavicons) return; + + var options = new ParallelOptions { MaxDegreeOfParallelism = 8, CancellationToken = cancellationToken }; + + await Parallel.ForEachAsync(bookmarks, options, async (bookmark, token) => + { + var cachePath = GetCachePath(bookmark.Url, _faviconCacheDir); + if (File.Exists(cachePath)) + { + bookmark.FaviconPath = cachePath; + return; + } + + // 1. Try local browser database + var localData = await _localExtractor.GetFaviconDataAsync(bookmark, token); + if (localData != null) + { + var (pngData, _) = await ToPng(new MemoryStream(localData), token); + if (pngData != null) + { + await File.WriteAllBytesAsync(cachePath, pngData, token); + bookmark.FaviconPath = cachePath; + return; + } + } + + // 2. Fallback to web if enabled + if (_settings.FetchMissingFavicons && Uri.TryCreate(bookmark.Url, UriKind.Absolute, out var uri)) + { + var webFaviconPath = await GetFaviconFromWebAsync(uri, token); + if (!string.IsNullOrEmpty(webFaviconPath)) + { + bookmark.FaviconPath = webFaviconPath; + } + } + }); + } + + private Task GetFaviconFromWebAsync(Uri url, CancellationToken token) + { + if (url is null || (url.Scheme != "http" && url.Scheme != "https")) + { + return Task.FromResult(null); + } + var authority = url.GetLeftPart(UriPartial.Authority); + return _ongoingFetches.GetOrAdd(authority, key => FetchAndCacheFaviconAsync(new Uri(key), token)); + } + + private static string GetCachePath(string url, string cacheDir) + { + var hash = SHA1.HashData(Encoding.UTF8.GetBytes(url)); + var sb = new StringBuilder(hash.Length * 2); + foreach (byte b in hash) + { + sb.Append(b.ToString("x2")); + } + return Path.Combine(cacheDir, sb.ToString() + ".png"); + } + + private async Task FetchAndCacheFaviconAsync(Uri url, CancellationToken token) + { + var urlString = url.GetLeftPart(UriPartial.Authority); + var cachePath = GetCachePath(urlString, _faviconCacheDir); + if (File.Exists(cachePath)) return cachePath; + + using var overallCts = CancellationTokenSource.CreateLinkedTokenSource(token); + overallCts.CancelAfter(TimeSpan.FromSeconds(10)); + var linkedToken = overallCts.Token; + + FetchResult icoResult = default; + FetchResult htmlResult = default; + + try + { + var icoTask = FetchAndProcessUrlAsync(new Uri(url, "/favicon.ico"), linkedToken); + var htmlTask = FetchAndProcessHtmlAsync(url, linkedToken); + + await Task.WhenAll(icoTask, htmlTask); + + icoResult = icoTask.Result; + htmlResult = htmlTask.Result; + + var bestResult = SelectBestFavicon(icoResult, htmlResult); + + if (bestResult.TempPath != null) + { + File.Move(bestResult.TempPath, cachePath, true); + _context.API.LogDebug(nameof(FaviconService), $"Favicon for {urlString} cached successfully."); + return cachePath; + } + + _context.API.LogDebug(nameof(FaviconService), $"No suitable favicon found for {urlString} after all tasks."); + } + catch (OperationCanceledException) { /* Swallow cancellation */ } + catch (Exception ex) + { + _context.API.LogException(nameof(FaviconService), $"Error in favicon fetch for {urlString}", ex); + } + finally + { + if (icoResult.TempPath != null && File.Exists(icoResult.TempPath)) File.Delete(icoResult.TempPath); + if (htmlResult.TempPath != null && File.Exists(htmlResult.TempPath)) File.Delete(htmlResult.TempPath); + _ongoingFetches.TryRemove(urlString, out _); + } + + return null; + } + + private FetchResult SelectBestFavicon(FetchResult icoResult, FetchResult htmlResult) + { + if (htmlResult.Size >= 32) return htmlResult; + if (icoResult.Size >= 32) return icoResult; + if (htmlResult.Size > icoResult.Size) return htmlResult; + // If sizes are equal, prefer ico as it's the standard. If htmlResult was better, it would likely have a larger size. + if (icoResult.Size >= 0) return icoResult; + if (htmlResult.Size >= 0) return htmlResult; + return default; + } + + private async Task FetchAndProcessHtmlAsync(Uri pageUri, CancellationToken token) + { + var bestCandidate = await GetBestCandidateFromHtmlAsync(pageUri, token); + if (bestCandidate != null && Uri.TryCreate(bestCandidate.Value.Url, UriKind.Absolute, out var candidateUri)) + { + return await FetchAndProcessUrlAsync(candidateUri, token); + } + return default; + } + + private async Task FetchAndProcessUrlAsync(Uri faviconUri, CancellationToken token) + { + var tempPath = Path.GetTempFileName(); + try + { + _context.API.LogDebug(nameof(FaviconService), $"Attempting to fetch favicon: {faviconUri}"); + using var request = new HttpRequestMessage(HttpMethod.Get, faviconUri); + var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, token); + + if (!response.IsSuccessStatusCode) + { + _context.API.LogDebug(nameof(FaviconService), $"Fetch failed for {faviconUri} with status code {response.StatusCode}"); + File.Delete(tempPath); + return default; + } + + await using var contentStream = await response.Content.ReadAsStreamAsync(token); + var (pngData, size) = await ToPng(contentStream, token); + + if (pngData is { Length: > 0 }) + { + await File.WriteAllBytesAsync(tempPath, pngData, token); + _context.API.LogDebug(nameof(FaviconService), $"Successfully processed favicon for {faviconUri} with original size {size}x{size}"); + return new FetchResult(tempPath, size); + } + + _context.API.LogDebug(nameof(FaviconService), $"Failed to process or invalid image for {faviconUri}."); + } + catch (OperationCanceledException) { _context.API.LogDebug(nameof(FaviconService), $"Favicon fetch cancelled for {faviconUri}."); } + catch (Exception ex) when (ex is HttpRequestException or NotSupportedException) + { + _context.API.LogDebug(nameof(FaviconService), $"Favicon fetch/process failed for {faviconUri}: {ex.Message}"); + } + + File.Delete(tempPath); + return default; + } + + private async Task GetBestCandidateFromHtmlAsync(Uri pageUri, CancellationToken token) + { + try + { + var response = await _httpClient.GetAsync(pageUri, HttpCompletionOption.ResponseHeadersRead, token); + if (!response.IsSuccessStatusCode) return null; + + var baseUri = response.RequestMessage?.RequestUri ?? pageUri; + + await using var stream = await response.Content.ReadAsStreamAsync(token); + using var reader = new StreamReader(stream, Encoding.UTF8, true); + + var contentBuilder = new StringBuilder(); + var buffer = new char[4096]; + int charsRead; + var totalCharsRead = 0; + const int maxCharsToRead = 500 * 1024; + + while (!token.IsCancellationRequested && (charsRead = await reader.ReadAsync(buffer, 0, buffer.Length)) > 0 && totalCharsRead < maxCharsToRead) + { + contentBuilder.Append(buffer, 0, charsRead); + totalCharsRead += charsRead; + + if (contentBuilder.ToString().Contains("", StringComparison.OrdinalIgnoreCase)) + { + break; + } + } + + return ParseLinkTags(contentBuilder.ToString(), baseUri) + .OrderByDescending(c => c.Score) + .FirstOrDefault(); + } + catch (OperationCanceledException) { return null; } + catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException) + { + _context.API.LogDebug(nameof(FaviconService), $"Failed to fetch or parse HTML head for {pageUri}: {ex.Message}"); + } + return null; + } + + private static List ParseLinkTags(string htmlContent, Uri originalBaseUri) + { + var candidates = new List(); + var effectiveBaseUri = originalBaseUri; + + var baseMatch = BaseHrefRegex().Match(htmlContent); + if (baseMatch.Success) + { + var baseHref = baseMatch.Groups["v"].Value; + if (Uri.TryCreate(originalBaseUri, baseHref, out var newBaseUri)) + { + effectiveBaseUri = newBaseUri; + } + } + + foreach (Match linkMatch in LinkTagRegex().Matches(htmlContent)) + { + var linkTag = linkMatch.Value; + var relMatch = RelAttributeRegex().Match(linkTag); + if (!relMatch.Success || !relMatch.Groups["v"].Value.Contains("icon", StringComparison.OrdinalIgnoreCase)) continue; + + var hrefMatch = HrefAttributeRegex().Match(linkTag); + if (!hrefMatch.Success) continue; + + var href = hrefMatch.Groups["v"].Value; + if (string.IsNullOrWhiteSpace(href)) continue; + + if (href.StartsWith("//")) + { + href = effectiveBaseUri.Scheme + ":" + href; + } + + if (!Uri.TryCreate(effectiveBaseUri, href, out var fullUrl)) continue; + + candidates.Add(new FaviconCandidate(fullUrl.ToString(), CalculateFaviconScore(linkTag, fullUrl.ToString()))); + } + + return candidates; + } + + private static int CalculateFaviconScore(string linkTag, string fullUrl) + { + var extension = Path.GetExtension(fullUrl).ToUpperInvariant(); + if (extension == ".SVG") return 10000; + + var sizesMatch = SizesAttributeRegex().Match(linkTag); + if (sizesMatch.Success) + { + var sizesValue = sizesMatch.Groups["v"].Value.ToUpperInvariant(); + if (sizesValue == "ANY") return 1000; + + var firstSizePart = sizesValue.Split(' ')[0]; + if (int.TryParse(firstSizePart.Split('X')[0], out var size)) + { + return size; + } + } + + if (extension == ".ICO") return 32; // Default score for .ico is 32 as it's likely to contain that size + + return 16; // Default score for other bitmaps + } + +private async Task<(byte[]? PngData, int Size)> ToPng(Stream stream, CancellationToken token) + { + token.ThrowIfCancellationRequested(); + + await using var ms = new MemoryStream(); + await stream.CopyToAsync(ms, token); + + ms.Position = 0; + + try + { + using var svg = new SKSvg(); + if (svg.Load(ms) is not null && svg.Picture is not null) + { + using var bitmap = new SKBitmap(32, 32); + using var canvas = new SKCanvas(bitmap); + canvas.Clear(SKColors.Transparent); + var scaleMatrix = SKMatrix.CreateScale(32 / svg.Picture.CullRect.Width, 32 / svg.Picture.CullRect.Height); + canvas.DrawPicture(svg.Picture, in scaleMatrix); + + using var image = SKImage.FromBitmap(bitmap); + using var data = image.Encode(SKEncodedImageFormat.Png, 80); + return (data.ToArray(), 32); + } + } + catch { /* Not an SVG */ } + + ms.Position = 0; + try + { + var decoder = new IconBitmapDecoder(ms, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); + if (decoder.Frames.Any()) + { + var largestFrame = decoder.Frames.OrderByDescending(f => f.Width * f.Height).First(); + + await using var pngStream = new MemoryStream(); + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(largestFrame)); + encoder.Save(pngStream); + + pngStream.Position = 0; + using var original = SKBitmap.Decode(pngStream); + if (original != null) + { + var originalWidth = original.Width; + var info = new SKImageInfo(32, 32, original.ColorType, original.AlphaType); + using var resized = original.Resize(info, new SKSamplingOptions(SKCubicResampler.Mitchell)); + if (resized != null) + { + using var image = SKImage.FromBitmap(resized); + using var data = image.Encode(SKEncodedImageFormat.Png, 80); + return (data.ToArray(), originalWidth); + } + } + } + } + catch (Exception ex) + { + _context.API.LogDebug(nameof(FaviconService), $"Could not decode stream as ICO: {ex.Message}"); + } + + ms.Position = 0; + try + { + using var original = SKBitmap.Decode(ms); + if (original != null) + { + var originalWidth = original.Width; + var info = new SKImageInfo(32, 32, original.ColorType, original.AlphaType); + using var resized = original.Resize(info, new SKSamplingOptions(SKCubicResampler.Mitchell)); + if (resized != null) + { + using var image = SKImage.FromBitmap(resized); + using var data = image.Encode(SKEncodedImageFormat.Png, 80); + return (data.ToArray(), originalWidth); + } + } + } + catch (Exception ex) + { + _context.API.LogException(nameof(FaviconService), "Failed to decode or convert bitmap", ex); + return (null, 0); + } + + return (null, 0); + } + + public void Dispose() + { + _httpClient.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/FirefoxBookmarkLoader.cs new file mode 100644 index 00000000000..8d9419fb982 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/FirefoxBookmarkLoader.cs @@ -0,0 +1,106 @@ +#nullable enable +using Flow.Launcher.Plugin.BrowserBookmarks.Models; +using Microsoft.Data.Sqlite; +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Services; + +public class FirefoxBookmarkLoader : IBookmarkLoader +{ + private readonly string _browserName; + private readonly string _placesPath; + private readonly Action _logException; + private readonly string _tempPath; + + public string Name => _browserName; + + private const string QueryAllBookmarks = """ + SELECT moz_places.url, moz_bookmarks.title + FROM moz_places + INNER JOIN moz_bookmarks ON ( + moz_bookmarks.fk NOT NULL AND moz_bookmarks.title NOT NULL AND moz_bookmarks.fk = moz_places.id + ) + ORDER BY moz_places.visit_count DESC + """; + + public FirefoxBookmarkLoader(string browserName, string placesPath, string tempPath, Action logException) + { + _browserName = browserName; + _placesPath = placesPath; + _logException = logException; + _tempPath = tempPath; + } + + public async IAsyncEnumerable GetBookmarksAsync([EnumeratorCancellation] CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(_placesPath) || !File.Exists(_placesPath)) + yield break; + + var bookmarks = new List(); + string? tempDbPath = null; + + try + { + // First, try to read directly from the source to avoid a slow file copy + await ReadBookmarksFromDb(_placesPath, bookmarks, cancellationToken); + } + catch (SqliteException ex) when (ex.SqliteErrorCode is 5 or 6) // 5 is SQLITE_BUSY, 6 is SQLITE_LOCKED + { + // Fallback to copying the file if the database is locked (e.g., Firefox is open) + try + { + tempDbPath = Path.Combine(_tempPath, $"ff_places_{Guid.NewGuid()}.sqlite"); + File.Copy(_placesPath, tempDbPath, true); + await ReadBookmarksFromDb(tempDbPath, bookmarks, cancellationToken); + } + catch (Exception copyEx) + { + _logException(nameof(FirefoxBookmarkLoader), $"Failed to load {_browserName} bookmarks from fallback copy: {_placesPath}", copyEx); + } + } + catch (Exception e) + { + _logException(nameof(FirefoxBookmarkLoader), $"Failed to load {_browserName} bookmarks: {_placesPath}", e); + } + finally + { + if (tempDbPath != null && File.Exists(tempDbPath)) + { + try { File.Delete(tempDbPath); } + catch(Exception e) { _logException(nameof(FirefoxBookmarkLoader), $"Failed to delete temp db file {tempDbPath}", e); } + } + } + + foreach (var bookmark in bookmarks) + { + yield return bookmark; + } + } + + private async Task ReadBookmarksFromDb(string dbPath, ICollection bookmarks, CancellationToken cancellationToken) + { + var profilePath = Path.GetDirectoryName(dbPath) ?? string.Empty; + var connectionString = $"Data Source={dbPath};Mode=ReadOnly;Pooling=false;"; + + await using var dbConnection = new SqliteConnection(connectionString); + await dbConnection.OpenAsync(cancellationToken); + await using var command = new SqliteCommand(QueryAllBookmarks, dbConnection); + await using var reader = await command.ExecuteReaderAsync(cancellationToken); + + while (await reader.ReadAsync(cancellationToken)) + { + var title = reader["title"]?.ToString() ?? string.Empty; + var url = reader["url"]?.ToString(); + + if (!string.IsNullOrEmpty(url)) + { + bookmarks.Add(new Bookmark(title, url, _browserName, profilePath)); + } + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/FirefoxProfileFinder.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/FirefoxProfileFinder.cs new file mode 100644 index 00000000000..82659d81cdd --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/FirefoxProfileFinder.cs @@ -0,0 +1,83 @@ +#nullable enable +using System; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Services; + +public static class FirefoxProfileFinder +{ + public static string? GetFirefoxPlacesPath() + { + // Standard MSI installer path + var standardPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox"); + var placesPath = GetPlacesPathFromProfileDir(standardPath); + + return !string.IsNullOrEmpty(placesPath) ? placesPath : null; + } + + public static string? GetFirefoxMsixPlacesPath() + { + var packagesPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Packages"); + if (!Directory.Exists(packagesPath)) + return null; + + try + { + var firefoxPackageFolder = Directory.EnumerateDirectories(packagesPath, "Mozilla.Firefox*", SearchOption.TopDirectoryOnly).FirstOrDefault(); + if (firefoxPackageFolder == null) + return null; + + var profileFolderPath = Path.Combine(firefoxPackageFolder, @"LocalCache\Roaming\Mozilla\Firefox"); + return GetPlacesPathFromProfileDir(profileFolderPath); + } + catch + { + // Logged in the calling service + return null; + } + } + + public static string? GetPlacesPathFromProfileDir(string profileFolderPath) + { + var profileIni = Path.Combine(profileFolderPath, @"profiles.ini"); + if (!File.Exists(profileIni)) + return null; + + try + { + var iniContent = File.ReadAllText(profileIni); + // Try to find the default-release profile first, which is the most common case. + var profileSectionMatch = Regex.Match(iniContent, @"\[Profile[^\]]+\]\s*Name=default-release[\s\S]+?Path=([^\r\n]+)[\s\S]+?Default=1", RegexOptions.IgnoreCase); + + // Fallback to any default profile. + if (!profileSectionMatch.Success) + { + profileSectionMatch = Regex.Match(iniContent, @"\[Profile[^\]]+\][\s\S]+?Path=([^\r\n]+)[\s\S]+?Default=1", RegexOptions.IgnoreCase); + } + + // Fallback to the first available profile if no default is marked. + if (!profileSectionMatch.Success) + { + profileSectionMatch = Regex.Match(iniContent, @"\[Profile[^\]]+\][\s\S]+?Path=([^\r\n]+)"); + } + + if (!profileSectionMatch.Success) + return null; + + var path = profileSectionMatch.Groups[1].Value; + var isRelative = !path.Contains(':'); + + var profilePath = isRelative ? Path.Combine(profileFolderPath, path.Replace('/', Path.DirectorySeparatorChar)) : path; + var placesDb = Path.Combine(profilePath, "places.sqlite"); + + return File.Exists(placesDb) ? placesDb : null; + } + catch + { + // Logged in the calling service + return null; + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/IBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/IBookmarkLoader.cs new file mode 100644 index 00000000000..7512857d5d1 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/IBookmarkLoader.cs @@ -0,0 +1,11 @@ +using Flow.Launcher.Plugin.BrowserBookmarks.Models; +using System.Collections.Generic; +using System.Threading; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Services; + +public interface IBookmarkLoader +{ + string Name { get; } + IAsyncEnumerable GetBookmarksAsync(CancellationToken cancellationToken = default); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/LocalFaviconExtractor.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/LocalFaviconExtractor.cs new file mode 100644 index 00000000000..71c75d864db --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Services/LocalFaviconExtractor.cs @@ -0,0 +1,136 @@ +#nullable enable +using Flow.Launcher.Plugin.BrowserBookmarks.Models; +using Microsoft.Data.Sqlite; +using System; +using System.IO; +using System.IO.Compression; +using System.Threading; +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.Services; + +public class LocalFaviconExtractor +{ + private readonly PluginInitContext _context; + private readonly string _tempPath; + + public LocalFaviconExtractor(PluginInitContext context, string tempPath) + { + _context = context; + _tempPath = tempPath; + } + + public async Task GetFaviconDataAsync(Bookmark bookmark, CancellationToken token) + { + return bookmark.Source switch + { + var s when s.Contains("Firefox") => await GetFirefoxFaviconAsync(bookmark, token), + _ => await GetChromiumFaviconAsync(bookmark, token) // Default to Chromium + }; + } + + private Task GetChromiumFaviconAsync(Bookmark bookmark, CancellationToken token) + { + const string query = @" + SELECT b.image_data FROM favicon_bitmaps b + JOIN icon_mapping m ON b.icon_id = m.icon_id + WHERE m.page_url = @url + ORDER BY b.width DESC LIMIT 1"; + + return GetFaviconFromDbAsync(bookmark, "Favicons", query, null, token); + } + + private Task GetFirefoxFaviconAsync(Bookmark bookmark, CancellationToken token) + { + const string query = @" + SELECT i.data FROM moz_icons i + JOIN moz_icons_to_pages ip ON i.id = ip.icon_id + JOIN moz_pages_w_icons p ON ip.page_id = p.id + WHERE p.page_url = @url + ORDER BY i.width DESC LIMIT 1"; + + return GetFaviconFromDbAsync(bookmark, "favicons.sqlite", query, PostProcessFirefoxFavicon, token); + } + + private async Task GetFaviconFromDbAsync(Bookmark bookmark, string dbFileName, string query, + Func>? postProcessor, CancellationToken token) + { + var dbPath = Path.Combine(bookmark.ProfilePath, dbFileName); + if (!File.Exists(dbPath)) + return null; + + var tempDbPath = Path.Combine(_tempPath, $"{Path.GetFileNameWithoutExtension(dbFileName)}_{Guid.NewGuid()}{Path.GetExtension(dbFileName)}"); + + try + { + File.Copy(dbPath, tempDbPath, true); + + var connectionString = $"Data Source={tempDbPath};Mode=ReadOnly;Pooling=false;"; + await using var connection = new SqliteConnection(connectionString); + await connection.OpenAsync(token); + await using var cmd = connection.CreateCommand(); + cmd.CommandText = query; + cmd.Parameters.AddWithValue("@url", bookmark.Url); + + if (await cmd.ExecuteScalarAsync(token) is not byte[] data || data.Length == 0) + return null; + + _context.API.LogDebug(nameof(LocalFaviconExtractor), $"Extracted {data.Length} bytes for {bookmark.Url} from {dbFileName}."); + + return postProcessor != null ? await postProcessor(data, token) : data; + } + catch (Exception ex) + { + _context.API.LogException(nameof(LocalFaviconExtractor), $"Failed to extract favicon for {bookmark.Url} from {bookmark.Source}'s {dbFileName}", ex); + return null; + } + finally + { + CleanupTempFiles(tempDbPath); + } + } + + private async Task PostProcessFirefoxFavicon(byte[] imageData, CancellationToken token) + { + // Handle old GZipped favicons + if (imageData.Length > 2 && imageData[0] == 0x1f && imageData[1] == 0x8b) + { + await using var inputStream = new MemoryStream(imageData); + await using var gZipStream = new GZipStream(inputStream, CompressionMode.Decompress); + await using var outputStream = new MemoryStream(); + await gZipStream.CopyToAsync(outputStream, token); + return outputStream.ToArray(); + } + + return imageData; + } + + private void CleanupTempFiles(string mainTempDbPath) + { + // This method ensures that the main temp file and any of its associated files + // (e.g., -wal, -shm) are deleted. + try + { + var directory = Path.GetDirectoryName(mainTempDbPath); + var baseName = Path.GetFileName(mainTempDbPath); + if (directory == null || !Directory.Exists(directory)) return; + + foreach (var file in Directory.GetFiles(directory, baseName + "*")) + { + try + { + File.Delete(file); + } + catch (Exception ex) + { + // Log failure to delete a specific chunk, but don't stop the process + _context.API.LogException(nameof(LocalFaviconExtractor), $"Failed to delete temporary file chunk: {file}", ex); + } + } + } + catch (Exception ex) + { + _context.API.LogException(nameof(LocalFaviconExtractor), $"Failed to clean up temporary files for base: {mainTempDbPath}", ex); + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/ViewModels/CustomBrowserSettingViewModel.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/ViewModels/CustomBrowserSettingViewModel.cs new file mode 100644 index 00000000000..46b1aa8ce55 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/ViewModels/CustomBrowserSettingViewModel.cs @@ -0,0 +1,101 @@ +#nullable enable +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Flow.Launcher.Plugin.BrowserBookmarks.Models; +using Flow.Launcher.Plugin.BrowserBookmarks.Services; +using System; +using System.ComponentModel; +using System.IO; +using System.Windows.Forms; +using Flow.Launcher.Plugin.SharedCommands; + +namespace Flow.Launcher.Plugin.BrowserBookmarks.ViewModels; + +public partial class CustomBrowserSettingViewModel : ObservableObject +{ + private readonly CustomBrowser _originalBrowser; + private readonly Action _closeAction; + + [ObservableProperty] + private CustomBrowser _editableBrowser; + + public string DetectedEngineText + { + get + { + if (string.IsNullOrEmpty(EditableBrowser.DataDirectoryPath)) + { + return Localize.flowlauncher_plugin_browserbookmark_engine_detection_select_directory(); + } + + return EditableBrowser.BrowserType switch + { + BrowserType.Unknown => Localize.flowlauncher_plugin_browserbookmark_engine_detection_invalid(), + BrowserType.Chromium => Localize.flowlauncher_plugin_browserbookmark_engine_detection_chromium(), + BrowserType.Firefox => Localize.flowlauncher_plugin_browserbookmark_engine_detection_firefox(), + _ => string.Empty + }; + } + } + + public bool IsValidPath => EditableBrowser.BrowserType != BrowserType.Unknown; + + public CustomBrowserSettingViewModel(CustomBrowser browser, Action closeAction) + { + _originalBrowser = browser; + _closeAction = closeAction; + EditableBrowser = new CustomBrowser + { + Name = browser.Name, + DataDirectoryPath = browser.DataDirectoryPath + }; + EditableBrowser.PropertyChanged += EditableBrowser_PropertyChanged; + DetectEngineType(); + } + + private void EditableBrowser_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(CustomBrowser.DataDirectoryPath)) + { + DetectEngineType(); + } + } + + private void DetectEngineType() + { + EditableBrowser.BrowserType = BrowserDetector.DetectBrowserType(EditableBrowser.DataDirectoryPath); + OnPropertyChanged(nameof(DetectedEngineText)); + OnPropertyChanged(nameof(IsValidPath)); + SaveCommand.NotifyCanExecuteChanged(); + } + + [RelayCommand(CanExecute = nameof(IsValidPath))] + private void Save() + { + _originalBrowser.Name = EditableBrowser.Name; + _originalBrowser.DataDirectoryPath = EditableBrowser.DataDirectoryPath; + _originalBrowser.BrowserType = EditableBrowser.BrowserType; + _closeAction(true); + } + + [RelayCommand] + private void Cancel() + { + _closeAction(false); + } + + [RelayCommand] + private void BrowseDataDirectory() + { + var dialog = new FolderBrowserDialog(); + if (!string.IsNullOrEmpty(EditableBrowser.DataDirectoryPath) && Directory.Exists(EditableBrowser.DataDirectoryPath)) + { + dialog.SelectedPath = EditableBrowser.DataDirectoryPath; + } + + if (dialog.ShowDialog() == DialogResult.OK) + { + EditableBrowser.DataDirectoryPath = dialog.SelectedPath; + } + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Views/CustomBrowserSetting.xaml similarity index 81% rename from Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml rename to Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Views/CustomBrowserSetting.xaml index f67d359bffa..a24f54d269f 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmarks/Views/CustomBrowserSetting.xaml @@ -1,12 +1,14 @@ - - + - - - @@ -38,7 +38,7 @@