mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 11:37:47 +01:00
feat: return sub-exts when extension type exts themselves are matched (#928)
Take the 'Spotify Control' extension as an example: - Spotify Control - Toggle Play/Pause - Next Track - Previous Track Previously, these sub-extensions were only returned when the query string matched them, and thus counterintuitively, searching for 'Spotify Control' would not hit them. This commit changes that behavior: when a main extension (of type Extension) matches the query, all of its sub-extensions are now included in the results.
This commit is contained in:
@@ -17,6 +17,7 @@ feat: support switching groups via keyboard shortcuts #911
|
||||
feat: support opening logs from about page #915
|
||||
feat: support moving cursor with home and end keys #918
|
||||
feat: support pageup/pagedown to navigate search results #920
|
||||
feat: return sub-exts when extension type exts themselves are matched #928
|
||||
|
||||
### 🐛 Bug fix
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ use tauri_plugin_global_shortcut::GlobalShortcutExt;
|
||||
use tauri_plugin_global_shortcut::ShortcutState;
|
||||
|
||||
pub(crate) const EXTENSION_ID: &str = "Window Management";
|
||||
pub(crate) const EXTENSION_NAME_LOWERCASE: &str = "window management";
|
||||
|
||||
/// JSON file for this extension.
|
||||
pub(crate) const PLUGIN_JSON_FILE: &str = include_str!("./plugin.json");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use super::EXTENSION_ID;
|
||||
use super::EXTENSION_NAME_LOWERCASE;
|
||||
use crate::common::document::{DataSourceReference, Document};
|
||||
use crate::common::{
|
||||
error::SearchError,
|
||||
@@ -81,6 +82,16 @@ impl SearchSource for WindowManagementSearchSource {
|
||||
}
|
||||
}
|
||||
|
||||
// An "extension" type extension should return all its
|
||||
// sub-extensions when the query string matches its name.
|
||||
// To do this, we score the extension name and take that
|
||||
// into account.
|
||||
if let Some(main_extension_score) =
|
||||
calculate_text_similarity(&query_string_lowercase, &EXTENSION_NAME_LOWERCASE)
|
||||
{
|
||||
score += main_extension_score;
|
||||
}
|
||||
|
||||
score
|
||||
};
|
||||
|
||||
|
||||
63
src-tauri/src/extension/third_party/mod.rs
vendored
63
src-tauri/src/extension/third_party/mod.rs
vendored
@@ -15,6 +15,7 @@ use crate::common::search::QuerySource;
|
||||
use crate::common::search::SearchQuery;
|
||||
use crate::common::traits::SearchSource;
|
||||
use crate::extension::ExtensionBundleIdBorrowed;
|
||||
use crate::extension::ExtensionType;
|
||||
use crate::extension::calculate_text_similarity;
|
||||
use crate::extension::canonicalize_relative_page_path;
|
||||
use crate::util::platform::Platform;
|
||||
@@ -757,11 +758,22 @@ impl SearchSource for ThirdPartyExtensionsSearchSource {
|
||||
|
||||
for extension in extensions_read_lock.iter().filter(|ext| ext.enabled) {
|
||||
if extension.r#type.contains_sub_items() {
|
||||
let opt_main_extension_lowercase_name =
|
||||
if extension.r#type == ExtensionType::Extension {
|
||||
Some(extension.name.to_lowercase())
|
||||
} else {
|
||||
// None if it is of type `ExtensionType::Group`
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(ref commands) = extension.commands {
|
||||
for command in commands.iter().filter(|cmd| cmd.enabled) {
|
||||
if let Some(hit) =
|
||||
extension_to_hit(command, &query_lower, opt_data_source.as_deref())
|
||||
{
|
||||
if let Some(hit) = extension_to_hit(
|
||||
command,
|
||||
&query_lower,
|
||||
opt_data_source.as_deref(),
|
||||
opt_main_extension_lowercase_name.as_deref(),
|
||||
) {
|
||||
hits.push(hit);
|
||||
}
|
||||
}
|
||||
@@ -769,9 +781,12 @@ impl SearchSource for ThirdPartyExtensionsSearchSource {
|
||||
|
||||
if let Some(ref scripts) = extension.scripts {
|
||||
for script in scripts.iter().filter(|script| script.enabled) {
|
||||
if let Some(hit) =
|
||||
extension_to_hit(script, &query_lower, opt_data_source.as_deref())
|
||||
{
|
||||
if let Some(hit) = extension_to_hit(
|
||||
script,
|
||||
&query_lower,
|
||||
opt_data_source.as_deref(),
|
||||
opt_main_extension_lowercase_name.as_deref(),
|
||||
) {
|
||||
hits.push(hit);
|
||||
}
|
||||
}
|
||||
@@ -783,6 +798,7 @@ impl SearchSource for ThirdPartyExtensionsSearchSource {
|
||||
quicklink,
|
||||
&query_lower,
|
||||
opt_data_source.as_deref(),
|
||||
opt_main_extension_lowercase_name.as_deref(),
|
||||
) {
|
||||
hits.push(hit);
|
||||
}
|
||||
@@ -791,16 +807,19 @@ impl SearchSource for ThirdPartyExtensionsSearchSource {
|
||||
|
||||
if let Some(ref views) = extension.views {
|
||||
for view in views.iter().filter(|link| link.enabled) {
|
||||
if let Some(hit) =
|
||||
extension_to_hit(view, &query_lower, opt_data_source.as_deref())
|
||||
{
|
||||
if let Some(hit) = extension_to_hit(
|
||||
view,
|
||||
&query_lower,
|
||||
opt_data_source.as_deref(),
|
||||
opt_main_extension_lowercase_name.as_deref(),
|
||||
) {
|
||||
hits.push(hit);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(hit) =
|
||||
extension_to_hit(extension, &query_lower, opt_data_source.as_deref())
|
||||
extension_to_hit(extension, &query_lower, opt_data_source.as_deref(), None)
|
||||
{
|
||||
hits.push(hit);
|
||||
}
|
||||
@@ -839,10 +858,18 @@ pub(crate) async fn uninstall_extension(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Argument `opt_main_extension_lowercase_name`: If `extension` is a sub-extension
|
||||
/// of an `extension` type extension, then this argument contains the lowercase
|
||||
/// name of that extension. Otherwise, None.
|
||||
///
|
||||
/// This argument is needed as an "extension" type extension should return all its
|
||||
/// sub-extensions when the query string matches its name. To do this, we pass the
|
||||
/// extension name, score it and take that into account.
|
||||
pub(crate) fn extension_to_hit(
|
||||
extension: &Extension,
|
||||
query_lower: &str,
|
||||
opt_data_source: Option<&str>,
|
||||
opt_main_extension_lowercase_name: Option<&str>,
|
||||
) -> Option<(Document, f64)> {
|
||||
if !extension.searchable() {
|
||||
return None;
|
||||
@@ -865,14 +892,26 @@ pub(crate) fn extension_to_hit(
|
||||
if let Some(title_score) =
|
||||
calculate_text_similarity(&query_lower, &extension.name.to_lowercase())
|
||||
{
|
||||
total_score += title_score * 1.0; // Weight for title
|
||||
total_score += title_score;
|
||||
}
|
||||
|
||||
// Score based on alias match if available
|
||||
// Alias is considered less important than title, so it gets a lower weight.
|
||||
if let Some(alias) = &extension.alias {
|
||||
if let Some(alias_score) = calculate_text_similarity(&query_lower, &alias.to_lowercase()) {
|
||||
total_score += alias_score * 0.7; // Weight for alias
|
||||
total_score += alias_score;
|
||||
}
|
||||
}
|
||||
|
||||
// An "extension" type extension should return all its
|
||||
// sub-extensions when the query string matches its ID.
|
||||
// To do this, we score the extension ID and take that
|
||||
// into account.
|
||||
if let Some(main_extension_lowercase_id) = opt_main_extension_lowercase_name {
|
||||
if let Some(main_extension_score) =
|
||||
calculate_text_similarity(&query_lower, main_extension_lowercase_id)
|
||||
{
|
||||
total_score += main_extension_score;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -265,13 +265,6 @@ async fn query_coco_fusion_multi_query_sources(
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-rank the hits
|
||||
*/
|
||||
if n_sources > 1 {
|
||||
boosted_levenshtein_rerank(&query_keyword, &mut all_hits_grouped_by_source_id);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sort hits within each source by score (descending) in case data sources
|
||||
* do not sort them
|
||||
@@ -363,6 +356,13 @@ async fn query_coco_fusion_multi_query_sources(
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Re-rank the final hits
|
||||
*/
|
||||
if n_sources > 1 {
|
||||
boosted_levenshtein_rerank(&query_keyword, &mut final_hits_grouped_by_source_id);
|
||||
}
|
||||
|
||||
let mut final_hits = Vec::new();
|
||||
for (_source_id, hits) in final_hits_grouped_by_source_id {
|
||||
final_hits.extend(hits);
|
||||
|
||||
Reference in New Issue
Block a user