mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Introduced a MaxSuggestions property (min 1, max 10) to control the number of autocomplete suggestions shown in web search plugins. Updated the UI to allow user configuration and added localized labels for the new setting in all supported languages. Suggestions in results are now limited according to this setting.
211 lines
8.3 KiB
C#
211 lines
8.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Controls;
|
|
using Flow.Launcher.Plugin.SharedCommands;
|
|
|
|
namespace Flow.Launcher.Plugin.WebSearch
|
|
{
|
|
public class Main : IAsyncPlugin, ISettingProvider, IPluginI18n, IResultUpdated, IContextMenu
|
|
{
|
|
internal static PluginInitContext _context;
|
|
|
|
private Settings _settings;
|
|
private SettingsViewModel _viewModel;
|
|
|
|
internal const string Images = "Images";
|
|
internal static string DefaultImagesDirectory;
|
|
internal static string CustomImagesDirectory;
|
|
|
|
private readonly int scoreStandard = 50;
|
|
|
|
private readonly int scoreSuggestions = 48;
|
|
|
|
private readonly string SearchSourceGlobalPluginWildCardSign = "*";
|
|
|
|
|
|
public async Task<List<Result>> QueryAsync(Query query, CancellationToken token)
|
|
{
|
|
if (FilesFolders.IsLocationPathString(query.Search))
|
|
return new List<Result>();
|
|
|
|
var searchSourceList = new List<SearchSource>();
|
|
var results = new List<Result>();
|
|
|
|
foreach (SearchSource searchSource in _settings.SearchSources.Where(o => (o.ActionKeyword == query.ActionKeyword ||
|
|
o.ActionKeyword == SearchSourceGlobalPluginWildCardSign)
|
|
&& o.Enabled))
|
|
{
|
|
string keyword = string.Empty;
|
|
keyword = searchSource.ActionKeyword == SearchSourceGlobalPluginWildCardSign ? query.ToString() : query.Search;
|
|
var title = keyword;
|
|
string subtitle = _context.API.GetTranslation("flowlauncher_plugin_websearch_search") + " " + searchSource.Title;
|
|
|
|
// Action Keyword match apear on top
|
|
var score = searchSource.ActionKeyword == SearchSourceGlobalPluginWildCardSign ? scoreStandard : scoreStandard + 1;
|
|
|
|
// This populates the associated action keyword search entry
|
|
if (string.IsNullOrEmpty(keyword))
|
|
{
|
|
var result = new Result
|
|
{
|
|
Title = subtitle,
|
|
SubTitle = string.Empty,
|
|
IcoPath = searchSource.IconPath,
|
|
Score = score
|
|
};
|
|
|
|
results.Add(result);
|
|
}
|
|
else
|
|
{
|
|
var result = new Result
|
|
{
|
|
Title = title,
|
|
SubTitle = subtitle,
|
|
IcoPath = searchSource.IconPath,
|
|
ActionKeywordAssigned = searchSource.ActionKeyword == SearchSourceGlobalPluginWildCardSign ? string.Empty : searchSource.ActionKeyword,
|
|
Score = score,
|
|
Action = c =>
|
|
{
|
|
_context.API.OpenWebUrl(searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword)), searchSource.IsPrivateMode);
|
|
|
|
return true;
|
|
},
|
|
ContextData = searchSource.Url.Replace("{q}", Uri.EscapeDataString(keyword)),
|
|
};
|
|
|
|
results.Add(result);
|
|
}
|
|
|
|
ResultsUpdated?.Invoke(this, new ResultUpdatedEventArgs
|
|
{
|
|
Results = results,
|
|
Query = query
|
|
});
|
|
|
|
await UpdateResultsFromSuggestionAsync(results, keyword, subtitle, searchSource, query, token).ConfigureAwait(false);
|
|
|
|
if (token.IsCancellationRequested)
|
|
return null;
|
|
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
private async Task UpdateResultsFromSuggestionAsync(List<Result> results, string keyword, string subtitle,
|
|
SearchSource searchSource, Query query, CancellationToken token)
|
|
{
|
|
if (_settings.EnableSuggestion)
|
|
{
|
|
var suggestions = await SuggestionsAsync(keyword, subtitle, searchSource, token).ConfigureAwait(false);
|
|
var enumerable = suggestions?.ToList();
|
|
if (token.IsCancellationRequested || enumerable is not { Count: > 0 })
|
|
return;
|
|
|
|
results.AddRange(enumerable);
|
|
|
|
token.ThrowIfCancellationRequested();
|
|
}
|
|
}
|
|
|
|
private async Task<IEnumerable<Result>> SuggestionsAsync(string keyword, string subtitle, SearchSource searchSource, CancellationToken token)
|
|
{
|
|
var source = _settings.SelectedSuggestion;
|
|
if (source == null)
|
|
{
|
|
return new List<Result>();
|
|
}
|
|
//Suggestions appear below actual result, and appear above global action keyword match if non-global;
|
|
var score = searchSource.ActionKeyword == SearchSourceGlobalPluginWildCardSign ? scoreSuggestions : scoreSuggestions + 1;
|
|
|
|
var suggestions = await source.SuggestionsAsync(keyword, token).ConfigureAwait(false);
|
|
|
|
token.ThrowIfCancellationRequested();
|
|
|
|
var resultsFromSuggestion = suggestions?.Take(_settings.MaxSuggestions).Select(o => new Result
|
|
{
|
|
Title = o,
|
|
SubTitle = subtitle,
|
|
Score = score,
|
|
IcoPath = searchSource.IconPath,
|
|
ActionKeywordAssigned = searchSource.ActionKeyword == SearchSourceGlobalPluginWildCardSign ? string.Empty : searchSource.ActionKeyword,
|
|
Action = c =>
|
|
{
|
|
_context.API.OpenWebUrl(searchSource.Url.Replace("{q}", Uri.EscapeDataString(o)), searchSource.IsPrivateMode);
|
|
|
|
return true;
|
|
},
|
|
ContextData = searchSource.Url.Replace("{q}", Uri.EscapeDataString(o)),
|
|
});
|
|
return resultsFromSuggestion;
|
|
}
|
|
|
|
public List<Result> LoadContextMenus(Result selected)
|
|
{
|
|
if (selected?.ContextData == null || selected.ContextData is not string) return new List<Result>();
|
|
return new List<Result>() {
|
|
new Result
|
|
{
|
|
Title = _context.API.GetTranslation("flowlauncher_plugin_websearch_copyurl_title"),
|
|
SubTitle = _context.API.GetTranslation("flowlauncher_plugin_websearch_copyurl_subtitle"),
|
|
IcoPath = "Images/copylink.png",
|
|
Action = c =>
|
|
{
|
|
_context.API.CopyToClipboard(selected.ContextData as string);
|
|
|
|
return true;
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
public Task InitAsync(PluginInitContext context)
|
|
{
|
|
return Task.Run(Init);
|
|
|
|
void Init()
|
|
{
|
|
_context = context;
|
|
|
|
_settings = _context.API.LoadSettingJsonStorage<Settings>();
|
|
_viewModel = new SettingsViewModel(_settings);
|
|
|
|
var pluginDirectory = _context.CurrentPluginMetadata.PluginDirectory;
|
|
var bundledImagesDirectory = Path.Combine(pluginDirectory, Images);
|
|
|
|
// Default images directory is in the WebSearch's application folder
|
|
DefaultImagesDirectory = Path.Combine(pluginDirectory, Images);
|
|
FilesFolders.ValidateDataDirectory(bundledImagesDirectory, DefaultImagesDirectory);
|
|
|
|
// Custom images directory is in the WebSearch's data location folder
|
|
CustomImagesDirectory = Path.Combine(_context.CurrentPluginMetadata.PluginSettingsDirectoryPath, "CustomIcons");
|
|
};
|
|
}
|
|
|
|
#region ISettingProvider Members
|
|
|
|
public Control CreateSettingPanel()
|
|
{
|
|
return new SettingsControl(_context, _viewModel);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public string GetTranslatedPluginTitle()
|
|
{
|
|
return _context.API.GetTranslation("flowlauncher_plugin_websearch_plugin_name");
|
|
}
|
|
|
|
public string GetTranslatedPluginDescription()
|
|
{
|
|
return _context.API.GetTranslation("flowlauncher_plugin_websearch_plugin_description");
|
|
}
|
|
|
|
public event ResultUpdatedEventHandler ResultsUpdated;
|
|
}
|
|
}
|