├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md └── src └── CanIUse ├── CanIUse.sln ├── Community.PowerToys.Run.Plugin.CanIUse.UnitTests ├── Community.PowerToys.Run.Plugin.CanIUse.UnitTests.csproj └── MainTests.cs └── Community.PowerToys.Run.Plugin.CanIUse ├── Community.PowerToys.Run.Plugin.CanIUse.csproj ├── Helpers └── BrowserExtensions.cs ├── Images ├── caniuse.dark.png └── caniuse.light.png ├── Main.cs ├── Models ├── Browser.cs ├── BrowserStatDictionary.cs ├── BrowserVersionStatDictionary.cs ├── CanIUseData.cs ├── DataMatch.cs ├── Feature.cs └── Link.cs ├── Repositories └── DataRepository.cs ├── Settings └── CanIUseSettings.cs └── plugin.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [skttl] -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and package for release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | build: 12 | runs-on: windows-latest 13 | 14 | env: 15 | PRODUCT_NAME: CanIUse 16 | PROJECT_PATH: ./src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse 17 | SLN_PATH: ./src/CanIUse/CanIUse.sln 18 | 19 | steps: 20 | 21 | - name: Checkout repository 22 | uses: actions/checkout@v4 23 | with: 24 | fetch-depth: 0 25 | 26 | - name: Extract version from release tag 27 | id: extract_version 28 | run: | 29 | $version = '${{ github.event.release.tag_name }}' 30 | echo "VERSION=$version" >> $env:GITHUB_ENV 31 | shell: powershell 32 | 33 | - name: Replace version token in plugin.json 34 | run: | 35 | (Get-Content '${{ env.PROJECT_PATH }}/plugin.json') -replace '\$\(Version\)', '${{ env.VERSION }}' | Set-Content '${{ env.PROJECT_PATH }}/plugin.json' 36 | shell: powershell 37 | 38 | - name: Package 39 | run: | 40 | dotnet pack ${{ env.SLN_PATH }} -p:Platform=x64 -p:Version=${{ env.VERSION }} 41 | dotnet pack ${{ env.SLN_PATH }} -p:Platform=ARM64 -p:Version=${{ env.VERSION }} 42 | 43 | - name: Upload x64 to Release Action 44 | uses: Shopify/upload-to-release@v2.0.0 45 | with: 46 | name: ${{ env.PRODUCT_NAME }}-${{ env.VERSION }}-x64.zip 47 | path: ${{ env.PROJECT_PATH }}/${{ env.PRODUCT_NAME }}-${{ env.VERSION }}-x64.zip 48 | content-type: application/zip 49 | repo-token: ${{secrets.GITHUB_TOKEN}} 50 | 51 | - name: Upload ARM64 to Release Action 52 | uses: Shopify/upload-to-release@v2.0.0 53 | with: 54 | name: ${{ env.PRODUCT_NAME }}-${{ env.VERSION }}-ARM64.zip 55 | path: ${{ env.PROJECT_PATH }}/${{ env.PRODUCT_NAME }}-${{ env.VERSION }}-ARM64.zip 56 | content-type: application/zip 57 | repo-token: ${{secrets.GITHUB_TOKEN}} 58 | 59 | # Step to calculate SHA256 for the two files 60 | - name: Calculate SHA-256 Hash for x64 and ARM64 61 | id: calculate_sha 62 | run: | 63 | $x64_hash = Get-FileHash -Path "${{ env.PROJECT_PATH }}/${{ env.PRODUCT_NAME }}-${{ env.VERSION }}-x64.zip" -Algorithm SHA256 | Select-Object -ExpandProperty Hash 64 | $arm64_hash = Get-FileHash -Path "${{ env.PROJECT_PATH }}/${{ env.PRODUCT_NAME }}-${{ env.VERSION }}-ARM64.zip" -Algorithm SHA256 | Select-Object -ExpandProperty Hash 65 | echo "x64_SHA=$x64_hash" >> $env:GITHUB_ENV 66 | echo "ARM64_SHA=$arm64_hash" >> $env:GITHUB_ENV 67 | shell: powershell 68 | 69 | - name: update release 70 | id: update_release 71 | uses: tubone24/update_release@v1.0 72 | env: 73 | GITHUB_TOKEN: ${{ github.token }} 74 | with: 75 | is_append_body: 1 76 | body: | 77 | ## SHA-256 Hashes: 78 | - **x64 Hash**: \`${{ env.x64_SHA }}\` 79 | - **ARM64 Hash**: \`${{ env.ARM64_SHA }}\` 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | bin 3 | obj 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Søren Kottal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Can I Use 2 | 3 | PowerToys Run plugin which will search and present results from caniuse.com. 4 | 5 | ![Code_I8YObdOujG](https://github.com/user-attachments/assets/0be4dcfc-7296-4e47-9701-097661f15425) 6 | 7 | ## Settings 8 | 9 | You can configure the plugin to show your favorite browsers in the search results under the PowerToys Run settings. 10 | 11 | ![PowerToys Settings_RNYdDeLYbL](https://github.com/user-attachments/assets/5903a0ed-8ec0-4f1c-a081-6d533e0b81bb) 12 | 13 | ## Usage 14 | 15 | 1. Open PowerToys Run (default shortcut is Alt+Space). 16 | 2. Type `caniuse` followed by your search query. 17 | 3. Select a search result and press `Enter` to open it in browser. 18 | 19 | ## Installation 20 | 21 | ### Via [ptr](https://github.com/8LWXpg/ptr) 22 | 23 | ```shell 24 | ptr add CanIUse skttl/ptrun-caniuse 25 | ``` 26 | 27 | ### Manual install 28 | 29 | 1. Download the latest release of the CanIUse from the releases page. 30 | 2. Extract the zip file's contents to your PowerToys modules directory for the user (`%LOCALAPPDATA%\Microsoft\PowerToys\PowerToys Run\Plugins`). 31 | 3. Restart PowerToys. 32 | 33 | ## Acknowledgments 34 | 35 | - [Henrik Lau Eriksson](https://github.com/hlaueriksson) - for the awesome [Visual Studio template for building plugins](https://github.com/hlaueriksson/Community.PowerToys.Run.Plugin.Templates) 36 | - [Nick](https://github.com/N0I0C0K) - took inspiration from his GitHub workflows in his [PowerTranslator plugin for PowerToys](https://github.com/N0I0C0K/PowerTranslator) 37 | -------------------------------------------------------------------------------- /src/CanIUse/CanIUse.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.10.35027.167 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Community.PowerToys.Run.Plugin.CanIUse", "Community.PowerToys.Run.Plugin.CanIUse\Community.PowerToys.Run.Plugin.CanIUse.csproj", "{D10D969C-02D1-4E35-8C80-667A43F7E10E}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Community.PowerToys.Run.Plugin.CanIUse.UnitTests", "Community.PowerToys.Run.Plugin.CanIUse.UnitTests\Community.PowerToys.Run.Plugin.CanIUse.UnitTests.csproj", "{A56D18DC-C3F2-4F3B-8019-D78EB7E8E435}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|ARM64 = Debug|ARM64 12 | Debug|x64 = Debug|x64 13 | Release|ARM64 = Release|ARM64 14 | Release|x64 = Release|x64 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {D10D969C-02D1-4E35-8C80-667A43F7E10E}.Debug|ARM64.ActiveCfg = Debug|ARM64 18 | {D10D969C-02D1-4E35-8C80-667A43F7E10E}.Debug|ARM64.Build.0 = Debug|ARM64 19 | {D10D969C-02D1-4E35-8C80-667A43F7E10E}.Debug|x64.ActiveCfg = Debug|x64 20 | {D10D969C-02D1-4E35-8C80-667A43F7E10E}.Debug|x64.Build.0 = Debug|x64 21 | {D10D969C-02D1-4E35-8C80-667A43F7E10E}.Release|ARM64.ActiveCfg = Release|ARM64 22 | {D10D969C-02D1-4E35-8C80-667A43F7E10E}.Release|ARM64.Build.0 = Release|ARM64 23 | {D10D969C-02D1-4E35-8C80-667A43F7E10E}.Release|x64.ActiveCfg = Release|x64 24 | {D10D969C-02D1-4E35-8C80-667A43F7E10E}.Release|x64.Build.0 = Release|x64 25 | {A56D18DC-C3F2-4F3B-8019-D78EB7E8E435}.Debug|ARM64.ActiveCfg = Debug|ARM64 26 | {A56D18DC-C3F2-4F3B-8019-D78EB7E8E435}.Debug|ARM64.Build.0 = Debug|ARM64 27 | {A56D18DC-C3F2-4F3B-8019-D78EB7E8E435}.Debug|x64.ActiveCfg = Debug|x64 28 | {A56D18DC-C3F2-4F3B-8019-D78EB7E8E435}.Debug|x64.Build.0 = Debug|x64 29 | {A56D18DC-C3F2-4F3B-8019-D78EB7E8E435}.Release|ARM64.ActiveCfg = Release|ARM64 30 | {A56D18DC-C3F2-4F3B-8019-D78EB7E8E435}.Release|ARM64.Build.0 = Release|ARM64 31 | {A56D18DC-C3F2-4F3B-8019-D78EB7E8E435}.Release|x64.ActiveCfg = Release|x64 32 | {A56D18DC-C3F2-4F3B-8019-D78EB7E8E435}.Release|x64.Build.0 = Release|x64 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | GlobalSection(ExtensibilityGlobals) = postSolution 38 | SolutionGuid = {674926A0-C170-4B9A-9EAD-5892663DE4F6} 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse.UnitTests/Community.PowerToys.Run.Plugin.CanIUse.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0-windows 5 | x64;ARM64 6 | $(Platform) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse.UnitTests/MainTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Linq; 3 | 4 | namespace Community.PowerToys.Run.Plugin.CanIUse.UnitTests 5 | { 6 | [TestClass] 7 | public class MainTests 8 | { 9 | private Main main; 10 | 11 | [TestInitialize] 12 | public void TestInitialize() 13 | { 14 | main = new Main(); 15 | } 16 | 17 | [TestMethod] 18 | public void Query_should_return_results() 19 | { 20 | var results = main.Query(new("search")); 21 | 22 | Assert.IsNotNull(results.First()); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Community.PowerToys.Run.Plugin.CanIUse.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0-windows 5 | true 6 | x64;ARM64 7 | $(Platform) 8 | enable 9 | CanIUse 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | PreserveNewest 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 32 | 33 | 35 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Helpers/BrowserExtensions.cs: -------------------------------------------------------------------------------- 1 | using Community.PowerToys.Run.Plugin.CanIUse.Models; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Community.PowerToys.Run.Plugin.CanIUse.Helpers; 9 | 10 | public static class BrowserExtensions 11 | { 12 | public static Dictionary BrowserNames = new Dictionary 13 | { 14 | { Browser.android, "Android Browser" }, 15 | { Browser.and_chr, "Chrome for Android" }, 16 | { Browser.and_ff, "Firefox for Android" }, 17 | { Browser.and_qq, "QQ Browser" }, 18 | { Browser.and_uc, "UC Browser for Android" }, 19 | { Browser.baidu, "Baidu" }, 20 | { Browser.bb, "BB Browser" }, 21 | { Browser.chrome, "Chrome" }, 22 | { Browser.edge, "Edge" }, 23 | { Browser.firefox, "Firefox" }, 24 | { Browser.ie, "IE" }, 25 | { Browser.ie_mob, "IE Mobile" }, 26 | { Browser.ios_saf, "iOS Safari" }, 27 | { Browser.kaios, "KaiOS Browser" }, 28 | { Browser.op_mini, "Opera Mini" }, 29 | { Browser.op_mob, "Opera Mobile" }, 30 | { Browser.opera, "Opera" }, 31 | { Browser.safari, "Safari" }, 32 | { Browser.samsung, "Samsung Internet" } 33 | }; 34 | public static string ToFriendlyName(this Browser browser) 35 | { 36 | return BrowserNames.TryGetValue(browser, out var s) ? s : browser.ToString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Images/caniuse.dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/ptrun-caniuse/43ac1d733209859406535b08e39cfa708395681d/src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Images/caniuse.dark.png -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Images/caniuse.light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skttl/ptrun-caniuse/43ac1d733209859406535b08e39cfa708395681d/src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Images/caniuse.light.png -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Main.cs: -------------------------------------------------------------------------------- 1 | using Community.PowerToys.Run.Plugin.CanIUse.Models; 2 | using Community.PowerToys.Run.Plugin.CanIUse.Repositories; 3 | using Community.PowerToys.Run.Plugin.CanIUse.Settings; 4 | using ManagedCommon; 5 | using Microsoft.PowerToys.Settings.UI.Library; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Windows.Controls; 10 | using Wox.Infrastructure; 11 | using Wox.Plugin; 12 | using BrowserInfo = Wox.Plugin.Common.DefaultBrowserInfo; 13 | 14 | namespace Community.PowerToys.Run.Plugin.CanIUse 15 | { 16 | /// 17 | /// Main class of this plugin that implement all used interfaces. 18 | /// 19 | public class Main : IPlugin, ISettingProvider, IDisposable 20 | { 21 | private readonly DataRepository _dataRepository = new DataRepository(); 22 | 23 | /// 24 | /// ID of the plugin. 25 | /// 26 | public static string PluginID => "5C1A49E431F74D66BC62FD214D834609"; 27 | 28 | /// 29 | /// Name of the plugin. 30 | /// 31 | public string Name => "CanIUse"; 32 | 33 | /// 34 | /// Description of the plugin. 35 | /// 36 | public string Description => "Lookup browser support for different web features"; 37 | 38 | private PluginInitContext? Context { get; set; } 39 | 40 | private bool Disposed { get; set; } 41 | 42 | public IEnumerable AdditionalOptions => CanIUseSettings.GetAdditionalOptions(); 43 | 44 | /// 45 | /// Return a filtered list, based on the given query. 46 | /// 47 | /// The query to filter the list. 48 | /// A filtered list, can be empty when nothing was found. 49 | public List Query(Query query) 50 | { 51 | if (string.IsNullOrWhiteSpace(query.Search)) 52 | { 53 | return new List() 54 | { 55 | new Result 56 | { 57 | QueryTextDisplay = string.Empty, 58 | Title = "Search caniuse.com", 59 | SubTitle = Description, 60 | IcoPath = IconPath 61 | } 62 | }; 63 | } 64 | 65 | var results = new List(); 66 | 67 | var data = _dataRepository.GetData(query.Search.ToLower().Trim())?.Take((int)CanIUseSettings.Instance.MaxResults); 68 | 69 | if (data is not null) 70 | { 71 | results.AddRange(data.Select(CreateResult)); 72 | } 73 | 74 | if (results.Count == 0) 75 | { 76 | results.Add(new Result 77 | { 78 | Title = "No results", 79 | SubTitle = $"No results found on caniuse.com for \"{query.Search}\"", 80 | IcoPath = IconPath 81 | }); 82 | } 83 | 84 | return results; 85 | } 86 | 87 | /// 88 | /// Initialize the plugin with the given . 89 | /// 90 | /// The for this plugin. 91 | public void Init(PluginInitContext context) 92 | { 93 | Context = context ?? throw new ArgumentNullException(nameof(context)); 94 | Context.API.ThemeChanged += OnThemeChanged; 95 | UpdateIconPath(Context.API.GetCurrentTheme()); 96 | } 97 | 98 | /// 99 | public void Dispose() 100 | { 101 | Dispose(true); 102 | GC.SuppressFinalize(this); 103 | } 104 | 105 | /// 106 | /// Wrapper method for that dispose additional objects and events form the plugin itself. 107 | /// 108 | /// Indicate that the plugin is disposed. 109 | protected virtual void Dispose(bool disposing) 110 | { 111 | if (Disposed || !disposing) 112 | { 113 | return; 114 | } 115 | 116 | if (Context?.API != null) 117 | { 118 | Context.API.ThemeChanged -= OnThemeChanged; 119 | } 120 | 121 | Disposed = true; 122 | } 123 | private string IconPath { get; set; } = "Images/caniuse.light.png"; 124 | 125 | private void UpdateIconPath(Theme theme) => IconPath = theme == Theme.Light || theme == Theme.HighContrastWhite ? "Images/caniuse.light.png" : "Images/caniuse.dark.png"; 126 | 127 | private void OnThemeChanged(Theme currentTheme, Theme newTheme) => UpdateIconPath(newTheme); 128 | 129 | 130 | public Result CreateResult(DataMatch item) 131 | { 132 | return new Result 133 | { 134 | QueryTextDisplay = string.Empty, 135 | Title = $"{item.Feature.Title} " + 136 | $"{item.Feature.UsagePercent}%" + 137 | $"{(item.Feature.UsagePercentPartial != 0 ? $" + {item.Feature.UsagePercentPartial}% = {(item.Feature.UsagePercent + item.Feature.UsagePercentPartial)}%" : "")}", 138 | SubTitle = $"{item.Feature.Stats.ToString(CanIUseSettings.Instance.DefaultBrowsers)}{Environment.NewLine}{item.Feature.Description}", 139 | Action = action => Helper.OpenCommandInShell(BrowserInfo.Path, BrowserInfo.ArgumentsPattern, $"https://caniuse.com/#feat={item.Key}"), 140 | IcoPath = IconPath 141 | }; 142 | } 143 | 144 | public Control CreateSettingPanel() 145 | { 146 | throw new NotImplementedException(); 147 | } 148 | 149 | public void UpdateSettings(PowerLauncherPluginSettings settings) 150 | { 151 | CanIUseSettings.Instance.UpdateSettings(settings); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Models/Browser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | 8 | namespace Community.PowerToys.Run.Plugin.CanIUse.Models; 9 | 10 | [JsonConverter(typeof(JsonStringEnumConverter))] 11 | public enum Browser 12 | { 13 | ie, 14 | edge, 15 | firefox, 16 | chrome, 17 | safari, 18 | opera, 19 | ios_saf, 20 | op_mini, 21 | android, 22 | bb, 23 | op_mob, 24 | and_chr, 25 | and_ff, 26 | ie_mob, 27 | and_uc, 28 | samsung, 29 | and_qq, 30 | baidu, 31 | kaios 32 | } 33 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Models/BrowserStatDictionary.cs: -------------------------------------------------------------------------------- 1 | using Community.PowerToys.Run.Plugin.CanIUse.Helpers; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Community.PowerToys.Run.Plugin.CanIUse.Models; 9 | 10 | public class BrowserStatDictionary : Dictionary 11 | { 12 | public string ToString(Browser[]? browsers = null) 13 | { 14 | browsers = browsers ?? new[] { Browser.chrome, Browser.firefox, Browser.safari, Browser.edge }; 15 | 16 | var stats = this.Where(x => browsers.Contains(x.Key)); 17 | 18 | return string.Join(", ", stats.Select(x => $"{x.Key.ToFriendlyName()} {x.Value.GetSupport()}")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Models/BrowserVersionStatDictionary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Community.PowerToys.Run.Plugin.CanIUse.Models; 8 | 9 | public class BrowserVersionStatDictionary : Dictionary 10 | { 11 | private string GetSupportedRange(string token) 12 | { 13 | 14 | var supported = this.Where(x => x.Value.StartsWith(token)).ToList(); 15 | if (supported.Count > 1) 16 | { 17 | return $"{supported.First().Key}-{supported.Last().Key}{(supported.Last().Key.Equals(this.Last().Key) ? "+" : "")}"; 18 | } 19 | else if (supported.Count > 0) 20 | { 21 | return $"{supported.Last().Key}{(supported.Last().Key.Equals(this.Last().Key) ? "+" : "")}"; 22 | } 23 | 24 | return "N/A"; 25 | } 26 | 27 | public string GetSupportedRange() => GetSupportedRange("y"); 28 | public string GetPartiallySupportedRange() => GetSupportedRange("p"); 29 | 30 | public string GetSupport() 31 | { 32 | var supported = GetSupportedRange(); 33 | var partiallySupported = GetPartiallySupportedRange(); 34 | return $"{supported}{(partiallySupported != "N/A" ? $" ({partiallySupported}*)" : "")}"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Models/CanIUseData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | 8 | namespace Community.PowerToys.Run.Plugin.CanIUse.Models; 9 | 10 | public class CanIUseData 11 | { 12 | [JsonPropertyName("data")] 13 | public Dictionary Data { get; set; } = new Dictionary(); 14 | } 15 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Models/DataMatch.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Wox.Infrastructure; 3 | 4 | namespace Community.PowerToys.Run.Plugin.CanIUse.Models; 5 | 6 | public class DataMatch 7 | { 8 | public DataMatch(KeyValuePair item, string search) 9 | { 10 | Key = item.Key; 11 | Feature = item.Value; 12 | MatchResult = StringMatcher.FuzzySearch(search, $"{item.Key} {item.Value.Title} {item.Value.Description}"); 13 | } 14 | public string Key { get; set; } 15 | public Feature Feature { get; set; } 16 | public MatchResult MatchResult { get; set; } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Models/Feature.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | 8 | namespace Community.PowerToys.Run.Plugin.CanIUse.Models; 9 | 10 | public class Feature 11 | { 12 | [JsonPropertyName("title")] 13 | public required string Title { get; set; } 14 | 15 | [JsonPropertyName("description")] 16 | public string? Description { get; set; } 17 | 18 | [JsonPropertyName("stats")] 19 | public BrowserStatDictionary Stats { get; set; } = new BrowserStatDictionary(); 20 | 21 | [JsonPropertyName("usage_perc_y")] 22 | public decimal UsagePercent { get; set; } 23 | 24 | [JsonPropertyName("usage_perc_a")] 25 | public decimal UsagePercentPartial { get; set; } 26 | } 27 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Models/Link.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Text.Json.Serialization; 6 | using System.Threading.Tasks; 7 | 8 | namespace Community.PowerToys.Run.Plugin.CanIUse.Models; 9 | 10 | public class Link 11 | { 12 | [JsonPropertyName("url")] 13 | public string? Url { get; set; } 14 | 15 | [JsonPropertyName("title")] 16 | public string? Title { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Repositories/DataRepository.cs: -------------------------------------------------------------------------------- 1 | using Community.PowerToys.Run.Plugin.CanIUse.Models; 2 | using Community.PowerToys.Run.Plugin.CanIUse.Settings; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Text.Json; 10 | using System.Threading.Tasks; 11 | using Wox.Infrastructure; 12 | 13 | namespace Community.PowerToys.Run.Plugin.CanIUse.Repositories; 14 | 15 | public class DataRepository 16 | { 17 | private static string absPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location) ?? string.Empty; 18 | private static string dataFn = Path.Combine(absPath, "data.json") ?? string.Empty; 19 | 20 | public static void WriteFile(string filename, string data) 21 | { 22 | File.WriteAllText(filename, data); 23 | } 24 | 25 | public static string ReadFile(string filename) 26 | { 27 | return File.ReadAllText(filename); 28 | } 29 | 30 | public IEnumerable? GetData(string search) 31 | { 32 | if (string.IsNullOrWhiteSpace(search)) 33 | { 34 | return null; 35 | } 36 | 37 | if (!File.Exists(dataFn) || (File.GetLastWriteTime(dataFn).AddDays(CanIUseSettings.Instance.DataRefreshInterval) < DateTime.Now)) 38 | { 39 | DownloadData().GetAwaiter().GetResult(); 40 | } 41 | 42 | var data = JsonSerializer.Deserialize(ReadFile(dataFn)); 43 | 44 | if (data?.Data == null) { 45 | return null; 46 | } 47 | 48 | var ordered = data.Data 49 | .Select(x => new DataMatch(x, search)) 50 | .Where(x => x.MatchResult.IsSearchPrecisionScoreMet()) 51 | .OrderByDescending(x => x.MatchResult.RawScore); 52 | 53 | return ordered; 54 | } 55 | 56 | public async Task DownloadData() 57 | { 58 | using (HttpClient client = new HttpClient()) 59 | { 60 | HttpResponseMessage response = await client.GetAsync("https://raw.githubusercontent.com/Fyrd/caniuse/master/data.json"); 61 | if (response.IsSuccessStatusCode) 62 | { 63 | var jsonData = await response.Content.ReadAsStringAsync(); 64 | var data = JsonSerializer.Deserialize(jsonData); 65 | WriteFile(dataFn, JsonSerializer.Serialize(data)); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/Settings/CanIUseSettings.cs: -------------------------------------------------------------------------------- 1 | using Community.PowerToys.Run.Plugin.CanIUse.Helpers; 2 | using Community.PowerToys.Run.Plugin.CanIUse.Models; 3 | using Microsoft.PowerToys.Settings.UI.Library; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace Community.PowerToys.Run.Plugin.CanIUse.Settings; 9 | 10 | internal sealed class CanIUseSettings 11 | { 12 | private readonly bool _initialized; 13 | private static CanIUseSettings? instance; 14 | private static Browser[] FallbackDefaultBrowsers = { Browser.chrome, Browser.edge, Browser.firefox, Browser.safari, Browser.ios_saf }; 15 | private static double FallbackDataRefreshInterval = 14; 16 | private static double FallbackMaxResults = 5; 17 | internal Browser[] DefaultBrowsers { get; set; } = FallbackDefaultBrowsers; 18 | internal double DataRefreshInterval { get; set; } = FallbackDataRefreshInterval; 19 | internal double MaxResults { get; set; } = FallbackMaxResults; 20 | 21 | private CanIUseSettings() 22 | { 23 | // Init class properties with default values 24 | UpdateSettings(null); 25 | _initialized = true; 26 | } 27 | 28 | internal static CanIUseSettings Instance 29 | { 30 | get 31 | { 32 | if (instance == null) 33 | { 34 | instance = new CanIUseSettings(); 35 | } 36 | 37 | return instance; 38 | } 39 | } 40 | internal static List GetAdditionalOptions() 41 | { 42 | 43 | var optionList = new List() 44 | { 45 | new PluginAdditionalOption() 46 | { 47 | Key = nameof(DataRefreshInterval), 48 | DisplayLabel = "Refresh caniuse.com data if older than", 49 | DisplayDescription = $"Determine the number of days the cache the data from caniuse.com for.", 50 | PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Numberbox, 51 | NumberBoxMin = 0, 52 | NumberValue = FallbackDataRefreshInterval 53 | }, 54 | new PluginAdditionalOption() 55 | { 56 | Key = nameof(MaxResults), 57 | DisplayLabel = "Maximum number of results", 58 | DisplayDescription = $"Determine the maximum number of results to show when searching.", 59 | PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Numberbox, 60 | NumberBoxMin = 1, 61 | NumberValue = FallbackMaxResults 62 | } 63 | }; 64 | 65 | foreach (var browserValue in Enum.GetValues(typeof(Browser))) 66 | { 67 | var browser = (Browser)browserValue; 68 | optionList.Add(new PluginAdditionalOption() 69 | { 70 | Key = $"{nameof(DefaultBrowsers)}_{browser.ToString()}", 71 | DisplayLabel = $"Show {browser.ToFriendlyName()} support in search results", 72 | PluginOptionType = PluginAdditionalOption.AdditionalOptionType.Checkbox, 73 | TextValue = browser.ToString(), 74 | Value = FallbackDefaultBrowsers.Contains(browser) 75 | }); 76 | } 77 | 78 | return optionList; 79 | } 80 | internal void UpdateSettings(PowerLauncherPluginSettings? settings) 81 | { 82 | if ((settings is null || settings.AdditionalOptions is null) & _initialized) 83 | { 84 | return; 85 | } 86 | 87 | DefaultBrowsers = settings?.AdditionalOptions?.Where(x => x.Key.StartsWith(nameof(DefaultBrowsers)) && x.Value).Select(x => (Browser)Enum.Parse(typeof(Browser), x.TextValue)).ToArray() 88 | ?? FallbackDefaultBrowsers; 89 | 90 | DataRefreshInterval = (settings?.AdditionalOptions?.FirstOrDefault(x => x.Key == nameof(DataRefreshInterval))?.NumberValue) ?? FallbackDataRefreshInterval; 91 | 92 | MaxResults = (settings?.AdditionalOptions?.FirstOrDefault(x => x.Key == nameof(MaxResults))?.NumberValue) ?? FallbackMaxResults; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/CanIUse/Community.PowerToys.Run.Plugin.CanIUse/plugin.json: -------------------------------------------------------------------------------- 1 | { 2 | "ID": "5C1A49E431F74D66BC62FD214D834609", 3 | "ActionKeyword": "caniuse", 4 | "IsGlobal": false, 5 | "Name": "CanIUse", 6 | "Author": "skttl", 7 | "Version": "$(Version)", 8 | "Language": "csharp", 9 | "Website": "https://github.com/skttl/ptrun-caniuse", 10 | "ExecuteFileName": "Community.PowerToys.Run.Plugin.CanIUse.dll", 11 | "IcoPathDark": "Images\\caniuse.dark.png", 12 | "IcoPathLight": "Images\\caniuse.light.png", 13 | "DynamicLoading": false 14 | } 15 | --------------------------------------------------------------------------------