mirror of https://github.com/Wox-launcher/Wox
feat(mru): Implement Most Recently Used (MRU) functionality across plugins
- Added MRU support to SysPlugin and UrlPlugin, allowing users to access recently used commands and URLs. - Introduced new data structures for MRU items and context data. - Updated the query methods to include MRU results based on user settings. - Modified the settings management to accommodate the new query mode for MRU. - Enhanced the UI to reflect the changes in query modes and added relevant translations. - Created a dedicated MRU manager for handling MRU items in the database. - Updated Flutter UI to support MRU queries and settings.
This commit is contained in:
parent
9f9cf84ea8
commit
4b2018934d
|
@ -36,6 +36,19 @@ type Oplog struct {
|
||||||
SyncedToCloud bool `gorm:"default:false"`
|
SyncedToCloud bool `gorm:"default:false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MRURecord struct {
|
||||||
|
Hash string `gorm:"primaryKey"` // MD5 hash of pluginId+title+subTitle
|
||||||
|
PluginID string `gorm:"not null"`
|
||||||
|
Title string `gorm:"not null"`
|
||||||
|
SubTitle string
|
||||||
|
Icon string // JSON serialized WoxImage
|
||||||
|
ContextData string // Plugin context data for restoration
|
||||||
|
LastUsed int64 `gorm:"not null"`
|
||||||
|
UseCount int `gorm:"default:1"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
func Init(ctx context.Context) error {
|
func Init(ctx context.Context) error {
|
||||||
dbPath := filepath.Join(util.GetLocation().GetUserDataDirectory(), "wox.db")
|
dbPath := filepath.Join(util.GetLocation().GetUserDataDirectory(), "wox.db")
|
||||||
|
|
||||||
|
@ -86,6 +99,7 @@ func Init(ctx context.Context) error {
|
||||||
&WoxSetting{},
|
&WoxSetting{},
|
||||||
&PluginSetting{},
|
&PluginSetting{},
|
||||||
&Oplog{},
|
&Oplog{},
|
||||||
|
&MRURecord{},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to migrate database schema: %w", err)
|
return fmt.Errorf("failed to migrate database schema: %w", err)
|
||||||
|
|
|
@ -163,6 +163,9 @@ func main() {
|
||||||
// Start auto backup if enabled
|
// Start auto backup if enabled
|
||||||
setting.GetSettingManager().StartAutoBackup(ctx)
|
setting.GetSettingManager().StartAutoBackup(ctx)
|
||||||
|
|
||||||
|
// Start MRU cleanup
|
||||||
|
setting.GetSettingManager().StartMRUCleanup(ctx)
|
||||||
|
|
||||||
// Start auto update checker if enabled
|
// Start auto update checker if enabled
|
||||||
updater.StartAutoUpdateChecker(ctx)
|
updater.StartAutoUpdateChecker(ctx)
|
||||||
|
|
||||||
|
|
|
@ -224,7 +224,7 @@ func Run(ctx context.Context) error {
|
||||||
"HideOnLostFocus": oldSettings.HideOnLostFocus,
|
"HideOnLostFocus": oldSettings.HideOnLostFocus,
|
||||||
"ShowTray": oldSettings.ShowTray,
|
"ShowTray": oldSettings.ShowTray,
|
||||||
"LangCode": oldSettings.LangCode,
|
"LangCode": oldSettings.LangCode,
|
||||||
"LastQueryMode": oldSettings.LastQueryMode,
|
"QueryMode": oldSettings.LastQueryMode, // Migrate LastQueryMode to QueryMode
|
||||||
"ShowPosition": oldSettings.ShowPosition,
|
"ShowPosition": oldSettings.ShowPosition,
|
||||||
"EnableAutoBackup": oldSettings.EnableAutoBackup,
|
"EnableAutoBackup": oldSettings.EnableAutoBackup,
|
||||||
"EnableAutoUpdate": oldSettings.EnableAutoUpdate,
|
"EnableAutoUpdate": oldSettings.EnableAutoUpdate,
|
||||||
|
|
|
@ -37,6 +37,7 @@ type API interface {
|
||||||
OnGetDynamicSetting(ctx context.Context, callback func(key string) string)
|
OnGetDynamicSetting(ctx context.Context, callback func(key string) string)
|
||||||
OnDeepLink(ctx context.Context, callback func(arguments map[string]string))
|
OnDeepLink(ctx context.Context, callback func(arguments map[string]string))
|
||||||
OnUnload(ctx context.Context, callback func())
|
OnUnload(ctx context.Context, callback func())
|
||||||
|
OnMRURestore(ctx context.Context, callback func(mruData MRUData) (*QueryResult, error))
|
||||||
RegisterQueryCommands(ctx context.Context, commands []MetadataCommand)
|
RegisterQueryCommands(ctx context.Context, commands []MetadataCommand)
|
||||||
AIChatStream(ctx context.Context, model common.Model, conversations []common.Conversation, options common.ChatOptions, callback common.ChatStreamFunc) error
|
AIChatStream(ctx context.Context, model common.Model, conversations []common.Conversation, options common.ChatOptions, callback common.ChatStreamFunc) error
|
||||||
}
|
}
|
||||||
|
@ -305,6 +306,15 @@ func (a *APIImpl) applyStartTimeIfAbsent(streamResult *common.ChatStreamData) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *APIImpl) OnMRURestore(ctx context.Context, callback func(mruData MRUData) (*QueryResult, error)) {
|
||||||
|
if !a.pluginInstance.Metadata.IsSupportFeature(MetadataFeatureMRU) {
|
||||||
|
a.Log(ctx, LogLevelError, "plugin has no access to MRU feature")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.pluginInstance.MRURestoreCallbacks = append(a.pluginInstance.MRURestoreCallbacks, callback)
|
||||||
|
}
|
||||||
|
|
||||||
func NewAPI(instance *Instance) API {
|
func NewAPI(instance *Instance) API {
|
||||||
apiImpl := &APIImpl{pluginInstance: instance}
|
apiImpl := &APIImpl{pluginInstance: instance}
|
||||||
logFolder := path.Join(util.GetLocation().GetLogPluginDirectory(), instance.Metadata.Name)
|
logFolder := path.Join(util.GetLocation().GetLogPluginDirectory(), instance.Metadata.Name)
|
||||||
|
|
|
@ -19,6 +19,7 @@ type Instance struct {
|
||||||
SettingChangeCallbacks []func(key string, value string)
|
SettingChangeCallbacks []func(key string, value string)
|
||||||
DeepLinkCallbacks []func(arguments map[string]string)
|
DeepLinkCallbacks []func(arguments map[string]string)
|
||||||
UnloadCallbacks []func()
|
UnloadCallbacks []func()
|
||||||
|
MRURestoreCallbacks []func(mruData MRUData) (*QueryResult, error) // MRU restore callbacks
|
||||||
|
|
||||||
// for measure performance
|
// for measure performance
|
||||||
LoadStartTimestamp int64
|
LoadStartTimestamp int64
|
||||||
|
|
|
@ -820,6 +820,8 @@ func (m *Manager) PolishResult(ctx context.Context, pluginInstance *Instance, qu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
originalIcon := result.Icon
|
||||||
|
|
||||||
// convert icon
|
// convert icon
|
||||||
result.Icon = common.ConvertIcon(ctx, result.Icon, pluginInstance.PluginDirectory)
|
result.Icon = common.ConvertIcon(ctx, result.Icon, pluginInstance.PluginDirectory)
|
||||||
for i := range result.Tails {
|
for i := range result.Tails {
|
||||||
|
@ -883,6 +885,7 @@ func (m *Manager) PolishResult(ctx context.Context, pluginInstance *Instance, qu
|
||||||
ResultTitle: result.Title,
|
ResultTitle: result.Title,
|
||||||
ResultSubTitle: result.SubTitle,
|
ResultSubTitle: result.SubTitle,
|
||||||
ContextData: result.ContextData,
|
ContextData: result.ContextData,
|
||||||
|
Icon: originalIcon,
|
||||||
PluginInstance: pluginInstance,
|
PluginInstance: pluginInstance,
|
||||||
Query: query,
|
Query: query,
|
||||||
Actions: util.NewHashMap[string, func(ctx context.Context, actionContext ActionContext)](),
|
Actions: util.NewHashMap[string, func(ctx context.Context, actionContext ActionContext)](),
|
||||||
|
@ -1385,13 +1388,41 @@ func (m *Manager) ExecuteAction(ctx context.Context, resultId string, actionId s
|
||||||
ContextData: resultCache.ContextData,
|
ContextData: resultCache.ContextData,
|
||||||
})
|
})
|
||||||
|
|
||||||
util.Go(ctx, fmt.Sprintf("[%s] add actioned result", resultCache.PluginInstance.Metadata.Name), func() {
|
util.Go(ctx, fmt.Sprintf("[%s] post execute action", resultCache.PluginInstance.Metadata.Name), func() {
|
||||||
setting.GetSettingManager().AddActionedResult(ctx, resultCache.PluginInstance.Metadata.Id, resultCache.ResultTitle, resultCache.ResultSubTitle, resultCache.Query.RawQuery)
|
m.postExecuteAction(ctx, resultCache)
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) postExecuteAction(ctx context.Context, resultCache *QueryResultCache) {
|
||||||
|
// Add actioned result for statistics
|
||||||
|
setting.GetSettingManager().AddActionedResult(ctx, resultCache.PluginInstance.Metadata.Id, resultCache.ResultTitle, resultCache.ResultSubTitle, resultCache.Query.RawQuery)
|
||||||
|
|
||||||
|
// Add to MRU if plugin supports it
|
||||||
|
if resultCache.PluginInstance.Metadata.IsSupportFeature(MetadataFeatureMRU) {
|
||||||
|
mruItem := setting.MRUItem{
|
||||||
|
PluginID: resultCache.PluginInstance.Metadata.Id,
|
||||||
|
Title: resultCache.ResultTitle,
|
||||||
|
SubTitle: resultCache.ResultSubTitle,
|
||||||
|
Icon: resultCache.Icon,
|
||||||
|
ContextData: resultCache.ContextData,
|
||||||
|
}
|
||||||
|
if err := setting.GetSettingManager().AddMRUItem(ctx, mruItem); err != nil {
|
||||||
|
util.GetLogger().Error(ctx, fmt.Sprintf("failed to add MRU item: %s", err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to query history only if query is not empty (skip empty queries like MRU)
|
||||||
|
if resultCache.Query.RawQuery != "" {
|
||||||
|
plainQuery := common.PlainQuery{
|
||||||
|
QueryType: resultCache.Query.Type,
|
||||||
|
QueryText: resultCache.Query.RawQuery,
|
||||||
|
}
|
||||||
|
setting.GetSettingManager().AddQueryHistory(ctx, plainQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) ExecuteRefresh(ctx context.Context, refreshableResultWithId RefreshableResultWithResultId) (RefreshableResultWithResultId, error) {
|
func (m *Manager) ExecuteRefresh(ctx context.Context, refreshableResultWithId RefreshableResultWithResultId) (RefreshableResultWithResultId, error) {
|
||||||
var refreshableResult RefreshableResult
|
var refreshableResult RefreshableResult
|
||||||
copyErr := copier.Copy(&refreshableResult, &refreshableResultWithId)
|
copyErr := copier.Copy(&refreshableResult, &refreshableResultWithId)
|
||||||
|
@ -1606,3 +1637,71 @@ func (m *Manager) ExecutePluginDeeplink(ctx context.Context, pluginId string, ar
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) QueryMRU(ctx context.Context) []QueryResultUI {
|
||||||
|
mruItems, err := setting.GetSettingManager().GetMRUItems(ctx, 10)
|
||||||
|
if err != nil {
|
||||||
|
util.GetLogger().Error(ctx, fmt.Sprintf("failed to get MRU items: %s", err.Error()))
|
||||||
|
return []QueryResultUI{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var results []QueryResultUI
|
||||||
|
for _, item := range mruItems {
|
||||||
|
pluginInstance := m.getPluginInstance(item.PluginID)
|
||||||
|
if pluginInstance == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !pluginInstance.Metadata.IsSupportFeature(MetadataFeatureMRU) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if restored := m.restoreFromMRU(ctx, pluginInstance, item); restored != nil {
|
||||||
|
polishedResult := m.PolishResult(ctx, pluginInstance, Query{}, *restored)
|
||||||
|
results = append(results, polishedResult.ToUI())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPluginInstance finds a plugin instance by ID
|
||||||
|
func (m *Manager) getPluginInstance(pluginID string) *Instance {
|
||||||
|
pluginInstance, found := lo.Find(m.instances, func(item *Instance) bool {
|
||||||
|
return item.Metadata.Id == pluginID
|
||||||
|
})
|
||||||
|
if found {
|
||||||
|
return pluginInstance
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// restoreFromMRU attempts to restore a QueryResult from MRU data
|
||||||
|
func (m *Manager) restoreFromMRU(ctx context.Context, pluginInstance *Instance, item setting.MRUItem) *QueryResult {
|
||||||
|
// For Go plugins, call MRU restore callbacks directly
|
||||||
|
if len(pluginInstance.MRURestoreCallbacks) > 0 {
|
||||||
|
mruData := MRUData{
|
||||||
|
PluginID: item.PluginID,
|
||||||
|
Title: item.Title,
|
||||||
|
SubTitle: item.SubTitle,
|
||||||
|
Icon: item.Icon,
|
||||||
|
ContextData: item.ContextData,
|
||||||
|
LastUsed: item.LastUsed,
|
||||||
|
UseCount: item.UseCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the first (and typically only) MRU restore callback
|
||||||
|
if restored, err := pluginInstance.MRURestoreCallbacks[0](mruData); err == nil {
|
||||||
|
return restored
|
||||||
|
} else {
|
||||||
|
util.GetLogger().Debug(ctx, fmt.Sprintf("MRU restore failed for plugin %s: %s", pluginInstance.Metadata.Name, err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For external plugins (Python/Node.js), MRU support will be implemented later
|
||||||
|
// Currently only Go plugins support MRU functionality
|
||||||
|
if pluginInstance.Host != nil {
|
||||||
|
util.GetLogger().Debug(ctx, fmt.Sprintf("External plugin MRU restore not yet implemented for plugin %s", pluginInstance.Metadata.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -37,6 +37,10 @@ const (
|
||||||
// by default, the width ratio is 0.5, which means the result list and preview panel have the same width
|
// by default, the width ratio is 0.5, which means the result list and preview panel have the same width
|
||||||
// if the width ratio is 0.3, which means the result list takes 30% of the width and the preview panel takes 70% of the width
|
// if the width ratio is 0.3, which means the result list takes 30% of the width and the preview panel takes 70% of the width
|
||||||
MetadataFeatureResultPreviewWidthRatio MetadataFeatureName = "resultPreviewWidthRatio"
|
MetadataFeatureResultPreviewWidthRatio MetadataFeatureName = "resultPreviewWidthRatio"
|
||||||
|
|
||||||
|
// enable this feature to support MRU (Most Recently Used) functionality
|
||||||
|
// plugin must implement OnMRURestore callback to restore results from MRU data
|
||||||
|
MetadataFeatureMRU MetadataFeatureName = "mru"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Metadata parsed from plugin.json, see `Plugin.json.md` for more detail
|
// Metadata parsed from plugin.json, see `Plugin.json.md` for more detail
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import "wox/common"
|
||||||
|
|
||||||
|
type MRUData struct {
|
||||||
|
PluginID string `json:"pluginId"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
SubTitle string `json:"subTitle"`
|
||||||
|
Icon common.WoxImage `json:"icon"`
|
||||||
|
ContextData string `json:"contextData"`
|
||||||
|
LastUsed int64 `json:"lastUsed"`
|
||||||
|
UseCount int `json:"useCount"`
|
||||||
|
}
|
|
@ -210,6 +210,7 @@ type QueryResultCache struct {
|
||||||
ResultTitle string
|
ResultTitle string
|
||||||
ResultSubTitle string
|
ResultSubTitle string
|
||||||
ContextData string
|
ContextData string
|
||||||
|
Icon common.WoxImage
|
||||||
Refresh func(context.Context, RefreshableResult) RefreshableResult
|
Refresh func(context.Context, RefreshableResult) RefreshableResult
|
||||||
PluginInstance *Instance
|
PluginInstance *Instance
|
||||||
Query Query
|
Query Query
|
||||||
|
|
|
@ -43,6 +43,12 @@ type appInfo struct {
|
||||||
Pid int `json:"-"`
|
Pid int `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type appContextData struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
func (a *appInfo) GetDisplayPath() string {
|
func (a *appInfo) GetDisplayPath() string {
|
||||||
if a.Type == AppTypeUWP {
|
if a.Type == AppTypeUWP {
|
||||||
return ""
|
return ""
|
||||||
|
@ -92,6 +98,11 @@ func (a *ApplicationPlugin) GetMetadata() plugin.Metadata {
|
||||||
"Macos",
|
"Macos",
|
||||||
"Linux",
|
"Linux",
|
||||||
},
|
},
|
||||||
|
Features: []plugin.MetadataFeature{
|
||||||
|
{
|
||||||
|
Name: plugin.MetadataFeatureMRU,
|
||||||
|
},
|
||||||
|
},
|
||||||
SettingDefinitions: []definition.PluginSettingDefinitionItem{
|
SettingDefinitions: []definition.PluginSettingDefinitionItem{
|
||||||
{
|
{
|
||||||
Type: definition.PluginSettingDefinitionTypeTable,
|
Type: definition.PluginSettingDefinitionTypeTable,
|
||||||
|
@ -140,6 +151,8 @@ func (a *ApplicationPlugin) Init(ctx context.Context, initParams plugin.InitPara
|
||||||
a.indexApps(ctx)
|
a.indexApps(ctx)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
a.api.OnMRURestore(ctx, a.handleMRURestore)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ApplicationPlugin) Query(ctx context.Context, query plugin.Query) []plugin.QueryResult {
|
func (a *ApplicationPlugin) Query(ctx context.Context, query plugin.Query) []plugin.QueryResult {
|
||||||
|
@ -149,12 +162,21 @@ func (a *ApplicationPlugin) Query(ctx context.Context, query plugin.Query) []plu
|
||||||
isPathNameMatch, pathNameScore := system.IsStringMatchScore(ctx, filepath.Base(info.Path), query.Search)
|
isPathNameMatch, pathNameScore := system.IsStringMatchScore(ctx, filepath.Base(info.Path), query.Search)
|
||||||
if isNameMatch || isPathNameMatch {
|
if isNameMatch || isPathNameMatch {
|
||||||
displayPath := info.GetDisplayPath()
|
displayPath := info.GetDisplayPath()
|
||||||
|
|
||||||
|
contextData := appContextData{
|
||||||
|
Name: info.Name,
|
||||||
|
Path: info.Path,
|
||||||
|
Type: info.Type,
|
||||||
|
}
|
||||||
|
contextDataJson, _ := json.Marshal(contextData)
|
||||||
|
|
||||||
result := plugin.QueryResult{
|
result := plugin.QueryResult{
|
||||||
Id: uuid.NewString(),
|
Id: uuid.NewString(),
|
||||||
Title: info.Name,
|
Title: info.Name,
|
||||||
SubTitle: displayPath,
|
SubTitle: displayPath,
|
||||||
Icon: info.Icon,
|
Icon: info.Icon,
|
||||||
Score: util.MaxInt64(nameScore, pathNameScore),
|
Score: util.MaxInt64(nameScore, pathNameScore),
|
||||||
|
ContextData: string(contextDataJson),
|
||||||
Actions: []plugin.QueryResultAction{
|
Actions: []plugin.QueryResultAction{
|
||||||
{
|
{
|
||||||
Name: "i18n:plugin_app_open",
|
Name: "i18n:plugin_app_open",
|
||||||
|
@ -525,3 +547,87 @@ func (a *ApplicationPlugin) removeDuplicateApps(ctx context.Context, apps []appI
|
||||||
a.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("removed %d duplicate apps, %d apps remaining", len(apps)-len(result), len(result)))
|
a.api.Log(ctx, plugin.LogLevelInfo, fmt.Sprintf("removed %d duplicate apps, %d apps remaining", len(apps)-len(result), len(result)))
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ApplicationPlugin) handleMRURestore(mruData plugin.MRUData) (*plugin.QueryResult, error) {
|
||||||
|
var contextData appContextData
|
||||||
|
if err := json.Unmarshal([]byte(mruData.ContextData), &contextData); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse context data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var appInfo *appInfo
|
||||||
|
for _, info := range a.apps {
|
||||||
|
if info.Name == contextData.Name && info.Path == contextData.Path {
|
||||||
|
appInfo = &info
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if appInfo == nil {
|
||||||
|
return nil, fmt.Errorf("app not found: %s", contextData.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
displayPath := appInfo.GetDisplayPath()
|
||||||
|
result := &plugin.QueryResult{
|
||||||
|
Id: uuid.NewString(),
|
||||||
|
Title: appInfo.Name,
|
||||||
|
SubTitle: displayPath,
|
||||||
|
Icon: mruData.Icon,
|
||||||
|
ContextData: mruData.ContextData,
|
||||||
|
Actions: []plugin.QueryResultAction{
|
||||||
|
{
|
||||||
|
Name: "i18n:plugin_app_open",
|
||||||
|
Icon: plugin.OpenIcon,
|
||||||
|
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||||
|
runErr := shell.Open(appInfo.Path)
|
||||||
|
if runErr != nil {
|
||||||
|
a.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("error opening app %s: %s", appInfo.Path, runErr.Error()))
|
||||||
|
a.api.Notify(ctx, fmt.Sprintf("i18n:plugin_app_open_failed_description: %s", runErr.Error()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "i18n:plugin_app_open_containing_folder",
|
||||||
|
Icon: plugin.OpenContainingFolderIcon,
|
||||||
|
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||||
|
if err := a.retriever.OpenAppFolder(ctx, *appInfo); err != nil {
|
||||||
|
a.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("error opening folder: %s", err.Error()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "i18n:plugin_app_copy_path",
|
||||||
|
Icon: plugin.CopyIcon,
|
||||||
|
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||||
|
clipboard.WriteText(appInfo.Path)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if appInfo.IsRunning() {
|
||||||
|
result.Actions = append(result.Actions, plugin.QueryResultAction{
|
||||||
|
Name: "i18n:plugin_app_terminate",
|
||||||
|
Icon: plugin.TerminateAppIcon,
|
||||||
|
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||||
|
p, getErr := os.FindProcess(appInfo.Pid)
|
||||||
|
if getErr != nil {
|
||||||
|
a.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("error finding process %d: %s", appInfo.Pid, getErr.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
killErr := p.Kill()
|
||||||
|
if killErr != nil {
|
||||||
|
a.api.Log(ctx, plugin.LogLevelError, fmt.Sprintf("error killing process %d: %s", appInfo.Pid, killErr.Error()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
result.RefreshInterval = 1000
|
||||||
|
result.OnRefresh = func(ctx context.Context, result plugin.RefreshableResult) plugin.RefreshableResult {
|
||||||
|
result.Tails = a.getRunningProcessResult(appInfo.Pid)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
@ -58,6 +58,9 @@ func (e emptyAPIImpl) AIChatStream(ctx context.Context, model common.Model, conv
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e emptyAPIImpl) OnMRURestore(ctx context.Context, callback func(mruData plugin.MRUData) (*plugin.QueryResult, error)) {
|
||||||
|
}
|
||||||
|
|
||||||
func TestMacRetriever_ParseAppInfo(t *testing.T) {
|
func TestMacRetriever_ParseAppInfo(t *testing.T) {
|
||||||
if util.IsMacOS() {
|
if util.IsMacOS() {
|
||||||
util.GetLocation().Init()
|
util.GetLocation().Init()
|
||||||
|
|
|
@ -2,6 +2,7 @@ package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -23,6 +24,11 @@ type Bookmark struct {
|
||||||
Url string
|
Url string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type bookmarkContextData struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
type BrowserBookmarkPlugin struct {
|
type BrowserBookmarkPlugin struct {
|
||||||
api plugin.API
|
api plugin.API
|
||||||
bookmarks []Bookmark
|
bookmarks []Bookmark
|
||||||
|
@ -49,6 +55,11 @@ func (c *BrowserBookmarkPlugin) GetMetadata() plugin.Metadata {
|
||||||
"Macos",
|
"Macos",
|
||||||
"Linux",
|
"Linux",
|
||||||
},
|
},
|
||||||
|
Features: []plugin.MetadataFeature{
|
||||||
|
{
|
||||||
|
Name: plugin.MetadataFeatureMRU,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +108,8 @@ func (c *BrowserBookmarkPlugin) Init(ctx context.Context, initParams plugin.Init
|
||||||
|
|
||||||
// Remove duplicate bookmarks (same name and url)
|
// Remove duplicate bookmarks (same name and url)
|
||||||
c.bookmarks = c.removeDuplicateBookmarks(c.bookmarks)
|
c.bookmarks = c.removeDuplicateBookmarks(c.bookmarks)
|
||||||
|
|
||||||
|
c.api.OnMRURestore(ctx, c.handleMRURestore)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *BrowserBookmarkPlugin) Query(ctx context.Context, query plugin.Query) (results []plugin.QueryResult) {
|
func (c *BrowserBookmarkPlugin) Query(ctx context.Context, query plugin.Query) (results []plugin.QueryResult) {
|
||||||
|
@ -122,11 +135,18 @@ func (c *BrowserBookmarkPlugin) Query(ctx context.Context, query plugin.Query) (
|
||||||
}
|
}
|
||||||
|
|
||||||
if isMatch {
|
if isMatch {
|
||||||
|
contextData := bookmarkContextData{
|
||||||
|
Name: bookmark.Name,
|
||||||
|
Url: bookmark.Url,
|
||||||
|
}
|
||||||
|
contextDataJson, _ := json.Marshal(contextData)
|
||||||
|
|
||||||
results = append(results, plugin.QueryResult{
|
results = append(results, plugin.QueryResult{
|
||||||
Title: bookmark.Name,
|
Title: bookmark.Name,
|
||||||
SubTitle: bookmark.Url,
|
SubTitle: bookmark.Url,
|
||||||
Score: matchScore,
|
Score: matchScore,
|
||||||
Icon: browserBookmarkIcon,
|
Icon: browserBookmarkIcon,
|
||||||
|
ContextData: string(contextDataJson),
|
||||||
Actions: []plugin.QueryResultAction{
|
Actions: []plugin.QueryResultAction{
|
||||||
{
|
{
|
||||||
Name: "i18n:plugin_browser_bookmark_open_in_browser",
|
Name: "i18n:plugin_browser_bookmark_open_in_browser",
|
||||||
|
@ -235,3 +255,40 @@ func (c *BrowserBookmarkPlugin) removeDuplicateBookmarks(bookmarks []Bookmark) [
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *BrowserBookmarkPlugin) handleMRURestore(mruData plugin.MRUData) (*plugin.QueryResult, error) {
|
||||||
|
var contextData bookmarkContextData
|
||||||
|
if err := json.Unmarshal([]byte(mruData.ContextData), &contextData); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse context data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if bookmark still exists in current bookmarks
|
||||||
|
found := false
|
||||||
|
for _, bookmark := range c.bookmarks {
|
||||||
|
if bookmark.Name == contextData.Name && bookmark.Url == contextData.Url {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("bookmark no longer exists: %s", contextData.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &plugin.QueryResult{
|
||||||
|
Title: contextData.Name,
|
||||||
|
SubTitle: contextData.Url,
|
||||||
|
Icon: mruData.Icon,
|
||||||
|
ContextData: mruData.ContextData,
|
||||||
|
Actions: []plugin.QueryResultAction{
|
||||||
|
{
|
||||||
|
Name: "i18n:plugin_browser_bookmark_open_in_browser",
|
||||||
|
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||||
|
shell.Open(contextData.Url)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
@ -270,3 +270,5 @@ func (m *mockAPI) RegisterQueryCommands(ctx context.Context, commands []plugin.M
|
||||||
func (m *mockAPI) AIChatStream(ctx context.Context, model common.Model, conversations []common.Conversation, options common.ChatOptions, callback common.ChatStreamFunc) error {
|
func (m *mockAPI) AIChatStream(ctx context.Context, model common.Model, conversations []common.Conversation, options common.ChatOptions, callback common.ChatStreamFunc) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func (m *mockAPI) OnMRURestore(ctx context.Context, callback func(mruData plugin.MRUData) (*plugin.QueryResult, error)) {
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"wox/common"
|
"wox/common"
|
||||||
"wox/i18n"
|
"wox/i18n"
|
||||||
|
@ -21,6 +22,12 @@ type IndicatorPlugin struct {
|
||||||
api plugin.API
|
api plugin.API
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type indicatorContextData struct {
|
||||||
|
TriggerKeyword string `json:"triggerKeyword"`
|
||||||
|
PluginID string `json:"pluginId"`
|
||||||
|
Command string `json:"command,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
func (i *IndicatorPlugin) GetMetadata() plugin.Metadata {
|
func (i *IndicatorPlugin) GetMetadata() plugin.Metadata {
|
||||||
return plugin.Metadata{
|
return plugin.Metadata{
|
||||||
Id: "38564bf0-75ad-4b3e-8afe-a0e0a287c42e",
|
Id: "38564bf0-75ad-4b3e-8afe-a0e0a287c42e",
|
||||||
|
@ -41,11 +48,17 @@ func (i *IndicatorPlugin) GetMetadata() plugin.Metadata {
|
||||||
"Macos",
|
"Macos",
|
||||||
"Linux",
|
"Linux",
|
||||||
},
|
},
|
||||||
|
Features: []plugin.MetadataFeature{
|
||||||
|
{
|
||||||
|
Name: plugin.MetadataFeatureMRU,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *IndicatorPlugin) Init(ctx context.Context, initParams plugin.InitParams) {
|
func (i *IndicatorPlugin) Init(ctx context.Context, initParams plugin.InitParams) {
|
||||||
i.api = initParams.API
|
i.api = initParams.API
|
||||||
|
i.api.OnMRURestore(ctx, i.handleMRURestore)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *IndicatorPlugin) Query(ctx context.Context, query plugin.Query) []plugin.QueryResult {
|
func (i *IndicatorPlugin) Query(ctx context.Context, query plugin.Query) []plugin.QueryResult {
|
||||||
|
@ -55,12 +68,19 @@ func (i *IndicatorPlugin) Query(ctx context.Context, query plugin.Query) []plugi
|
||||||
return triggerKeyword != "*" && IsStringMatchNoPinYin(ctx, triggerKeyword, query.Search)
|
return triggerKeyword != "*" && IsStringMatchNoPinYin(ctx, triggerKeyword, query.Search)
|
||||||
})
|
})
|
||||||
if found {
|
if found {
|
||||||
|
contextData := indicatorContextData{
|
||||||
|
TriggerKeyword: triggerKeyword,
|
||||||
|
PluginID: pluginInstance.Metadata.Id,
|
||||||
|
}
|
||||||
|
contextDataJson, _ := json.Marshal(contextData)
|
||||||
|
|
||||||
results = append(results, plugin.QueryResult{
|
results = append(results, plugin.QueryResult{
|
||||||
Id: uuid.NewString(),
|
Id: uuid.NewString(),
|
||||||
Title: triggerKeyword,
|
Title: triggerKeyword,
|
||||||
SubTitle: fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_indicator_activate_plugin"), pluginInstance.Metadata.Name),
|
SubTitle: fmt.Sprintf(i18n.GetI18nManager().TranslateWox(ctx, "plugin_indicator_activate_plugin"), pluginInstance.Metadata.Name),
|
||||||
Score: 10,
|
Score: 10,
|
||||||
Icon: pluginInstance.Metadata.GetIconOrDefault(pluginInstance.PluginDirectory, indicatorIcon),
|
Icon: pluginInstance.Metadata.GetIconOrDefault(pluginInstance.PluginDirectory, indicatorIcon),
|
||||||
|
ContextData: string(contextDataJson),
|
||||||
Actions: []plugin.QueryResultAction{
|
Actions: []plugin.QueryResultAction{
|
||||||
{
|
{
|
||||||
Name: "i18n:plugin_indicator_activate",
|
Name: "i18n:plugin_indicator_activate",
|
||||||
|
@ -101,3 +121,59 @@ func (i *IndicatorPlugin) Query(ctx context.Context, query plugin.Query) []plugi
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *IndicatorPlugin) handleMRURestore(mruData plugin.MRUData) (*plugin.QueryResult, error) {
|
||||||
|
var contextData indicatorContextData
|
||||||
|
if err := json.Unmarshal([]byte(mruData.ContextData), &contextData); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse context data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the plugin instance by ID
|
||||||
|
var pluginInstance *plugin.Instance
|
||||||
|
for _, instance := range plugin.GetPluginManager().GetPluginInstances() {
|
||||||
|
if instance.Metadata.Id == contextData.PluginID {
|
||||||
|
pluginInstance = instance
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pluginInstance == nil {
|
||||||
|
return nil, fmt.Errorf("plugin no longer exists: %s", contextData.PluginID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if trigger keyword still exists
|
||||||
|
triggerKeywords := pluginInstance.GetTriggerKeywords()
|
||||||
|
found := false
|
||||||
|
for _, keyword := range triggerKeywords {
|
||||||
|
if keyword == contextData.TriggerKeyword {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("trigger keyword no longer exists: %s", contextData.TriggerKeyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &plugin.QueryResult{
|
||||||
|
Id: uuid.NewString(),
|
||||||
|
Title: contextData.TriggerKeyword,
|
||||||
|
SubTitle: fmt.Sprintf(i18n.GetI18nManager().TranslateWox(context.Background(), "plugin_indicator_activate_plugin"), pluginInstance.Metadata.Name),
|
||||||
|
Icon: mruData.Icon,
|
||||||
|
ContextData: mruData.ContextData,
|
||||||
|
Actions: []plugin.QueryResultAction{
|
||||||
|
{
|
||||||
|
Name: "i18n:plugin_indicator_activate",
|
||||||
|
PreventHideAfterAction: true,
|
||||||
|
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||||
|
i.api.ChangeQuery(ctx, common.PlainQuery{
|
||||||
|
QueryType: plugin.QueryTypeInput,
|
||||||
|
QueryText: fmt.Sprintf("%s ", contextData.TriggerKeyword),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
@ -34,6 +35,10 @@ type SysCommand struct {
|
||||||
Action func(ctx context.Context, actionContext plugin.ActionContext)
|
Action func(ctx context.Context, actionContext plugin.ActionContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sysContextData struct {
|
||||||
|
CommandTitle string `json:"commandTitle"`
|
||||||
|
}
|
||||||
|
|
||||||
func (r *SysPlugin) GetMetadata() plugin.Metadata {
|
func (r *SysPlugin) GetMetadata() plugin.Metadata {
|
||||||
return plugin.Metadata{
|
return plugin.Metadata{
|
||||||
Id: "227f7d64-df08-4e35-ad05-98a26d540d06",
|
Id: "227f7d64-df08-4e35-ad05-98a26d540d06",
|
||||||
|
@ -55,6 +60,11 @@ func (r *SysPlugin) GetMetadata() plugin.Metadata {
|
||||||
"Macos",
|
"Macos",
|
||||||
"Linux",
|
"Linux",
|
||||||
},
|
},
|
||||||
|
Features: []plugin.MetadataFeature{
|
||||||
|
{
|
||||||
|
Name: plugin.MetadataFeatureMRU,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,11 +196,17 @@ func (r *SysPlugin) Query(ctx context.Context, query plugin.Query) (results []pl
|
||||||
}
|
}
|
||||||
|
|
||||||
if isTitleMatch {
|
if isTitleMatch {
|
||||||
|
contextData := sysContextData{
|
||||||
|
CommandTitle: command.Title,
|
||||||
|
}
|
||||||
|
contextDataJson, _ := json.Marshal(contextData)
|
||||||
|
|
||||||
results = append(results, plugin.QueryResult{
|
results = append(results, plugin.QueryResult{
|
||||||
Title: command.Title,
|
Title: command.Title,
|
||||||
SubTitle: command.SubTitle,
|
SubTitle: command.SubTitle,
|
||||||
Score: titleScore,
|
Score: titleScore,
|
||||||
Icon: command.Icon,
|
Icon: command.Icon,
|
||||||
|
ContextData: string(contextDataJson),
|
||||||
Actions: []plugin.QueryResultAction{
|
Actions: []plugin.QueryResultAction{
|
||||||
{
|
{
|
||||||
Name: "i18n:plugin_sys_execute",
|
Name: "i18n:plugin_sys_execute",
|
||||||
|
@ -200,6 +216,8 @@ func (r *SysPlugin) Query(ctx context.Context, query plugin.Query) (results []pl
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.api.OnMRURestore(ctx, r.handleMRURestore)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, instance := range plugin.GetPluginManager().GetPluginInstances() {
|
for _, instance := range plugin.GetPluginManager().GetPluginInstances() {
|
||||||
|
@ -236,3 +254,39 @@ func (r *SysPlugin) Query(ctx context.Context, query plugin.Query) (results []pl
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *SysPlugin) handleMRURestore(mruData plugin.MRUData) (*plugin.QueryResult, error) {
|
||||||
|
var contextData sysContextData
|
||||||
|
if err := json.Unmarshal([]byte(mruData.ContextData), &contextData); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse context data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the command by title
|
||||||
|
var foundCommand *SysCommand
|
||||||
|
for _, command := range r.commands {
|
||||||
|
if command.Title == contextData.CommandTitle {
|
||||||
|
foundCommand = &command
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundCommand == nil {
|
||||||
|
return nil, fmt.Errorf("system command no longer exists: %s", contextData.CommandTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &plugin.QueryResult{
|
||||||
|
Title: foundCommand.Title,
|
||||||
|
SubTitle: foundCommand.SubTitle,
|
||||||
|
Icon: mruData.Icon,
|
||||||
|
ContextData: mruData.ContextData,
|
||||||
|
Actions: []plugin.QueryResultAction{
|
||||||
|
{
|
||||||
|
Name: "i18n:plugin_sys_execute",
|
||||||
|
Action: foundCommand.Action,
|
||||||
|
PreventHideAfterAction: foundCommand.PreventHideAfterAction,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
@ -26,6 +26,12 @@ type UrlHistory struct {
|
||||||
Title string
|
Title string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type urlContextData struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Type string `json:"type"` // "history" or "direct"
|
||||||
|
}
|
||||||
|
|
||||||
type UrlPlugin struct {
|
type UrlPlugin struct {
|
||||||
api plugin.API
|
api plugin.API
|
||||||
reg *regexp.Regexp
|
reg *regexp.Regexp
|
||||||
|
@ -53,6 +59,11 @@ func (r *UrlPlugin) GetMetadata() plugin.Metadata {
|
||||||
"Macos",
|
"Macos",
|
||||||
"Linux",
|
"Linux",
|
||||||
},
|
},
|
||||||
|
Features: []plugin.MetadataFeature{
|
||||||
|
{
|
||||||
|
Name: plugin.MetadataFeatureMRU,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +71,8 @@ func (r *UrlPlugin) Init(ctx context.Context, initParams plugin.InitParams) {
|
||||||
r.api = initParams.API
|
r.api = initParams.API
|
||||||
r.reg = r.getReg()
|
r.reg = r.getReg()
|
||||||
r.recentUrls = r.loadRecentUrls(ctx)
|
r.recentUrls = r.loadRecentUrls(ctx)
|
||||||
|
|
||||||
|
r.api.OnMRURestore(ctx, r.handleMRURestore)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *UrlPlugin) loadRecentUrls(ctx context.Context) []UrlHistory {
|
func (r *UrlPlugin) loadRecentUrls(ctx context.Context) []UrlHistory {
|
||||||
|
@ -90,11 +103,19 @@ func (r *UrlPlugin) Query(ctx context.Context, query plugin.Query) (results []pl
|
||||||
})
|
})
|
||||||
|
|
||||||
for _, history := range existingUrlHistory {
|
for _, history := range existingUrlHistory {
|
||||||
|
contextData := urlContextData{
|
||||||
|
Url: history.Url,
|
||||||
|
Title: history.Title,
|
||||||
|
Type: "history",
|
||||||
|
}
|
||||||
|
contextDataJson, _ := json.Marshal(contextData)
|
||||||
|
|
||||||
results = append(results, plugin.QueryResult{
|
results = append(results, plugin.QueryResult{
|
||||||
Title: history.Url,
|
Title: history.Url,
|
||||||
SubTitle: history.Title,
|
SubTitle: history.Title,
|
||||||
Score: 100,
|
Score: 100,
|
||||||
Icon: history.Icon.Overlay(urlIcon, 0.4, 0.6, 0.6),
|
Icon: history.Icon.Overlay(urlIcon, 0.4, 0.6, 0.6),
|
||||||
|
ContextData: string(contextDataJson),
|
||||||
Actions: []plugin.QueryResultAction{
|
Actions: []plugin.QueryResultAction{
|
||||||
{
|
{
|
||||||
Name: "i18n:plugin_url_open",
|
Name: "i18n:plugin_url_open",
|
||||||
|
@ -119,11 +140,19 @@ func (r *UrlPlugin) Query(ctx context.Context, query plugin.Query) (results []pl
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.reg.FindStringIndex(query.Search)) > 0 {
|
if len(r.reg.FindStringIndex(query.Search)) > 0 {
|
||||||
|
contextData := urlContextData{
|
||||||
|
Url: query.Search,
|
||||||
|
Title: "",
|
||||||
|
Type: "direct",
|
||||||
|
}
|
||||||
|
contextDataJson, _ := json.Marshal(contextData)
|
||||||
|
|
||||||
results = append(results, plugin.QueryResult{
|
results = append(results, plugin.QueryResult{
|
||||||
Title: query.Search,
|
Title: query.Search,
|
||||||
SubTitle: "i18n:plugin_url_open_in_browser",
|
SubTitle: "i18n:plugin_url_open_in_browser",
|
||||||
Score: 100,
|
Score: 100,
|
||||||
Icon: urlIcon,
|
Icon: urlIcon,
|
||||||
|
ContextData: string(contextDataJson),
|
||||||
Actions: []plugin.QueryResultAction{
|
Actions: []plugin.QueryResultAction{
|
||||||
{
|
{
|
||||||
Name: "i18n:plugin_url_open",
|
Name: "i18n:plugin_url_open",
|
||||||
|
@ -202,3 +231,77 @@ func (r *UrlPlugin) removeRecentUrl(ctx context.Context, url string) {
|
||||||
|
|
||||||
r.api.SaveSetting(ctx, "recentUrls", string(urlsJson), false)
|
r.api.SaveSetting(ctx, "recentUrls", string(urlsJson), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *UrlPlugin) handleMRURestore(mruData plugin.MRUData) (*plugin.QueryResult, error) {
|
||||||
|
var contextData urlContextData
|
||||||
|
if err := json.Unmarshal([]byte(mruData.ContextData), &contextData); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse context data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
url := contextData.Url
|
||||||
|
if !strings.HasPrefix(url, "http") {
|
||||||
|
url = "https://" + url
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &plugin.QueryResult{
|
||||||
|
Title: contextData.Url,
|
||||||
|
SubTitle: contextData.Title,
|
||||||
|
Icon: mruData.Icon,
|
||||||
|
ContextData: mruData.ContextData,
|
||||||
|
}
|
||||||
|
|
||||||
|
if contextData.Type == "history" {
|
||||||
|
found := false
|
||||||
|
for _, history := range r.recentUrls {
|
||||||
|
if history.Url == contextData.Url {
|
||||||
|
found = true
|
||||||
|
result.SubTitle = history.Title
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, fmt.Errorf("URL no longer in history: %s", contextData.Url)
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Actions = []plugin.QueryResultAction{
|
||||||
|
{
|
||||||
|
Name: "i18n:plugin_url_open",
|
||||||
|
Icon: plugin.OpenIcon,
|
||||||
|
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||||
|
openErr := shell.Open(url)
|
||||||
|
if openErr != nil {
|
||||||
|
r.api.Log(ctx, "Error opening URL", openErr.Error())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "i18n:plugin_url_remove",
|
||||||
|
Icon: plugin.TrashIcon,
|
||||||
|
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||||
|
r.removeRecentUrl(ctx, contextData.Url)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.SubTitle = "i18n:plugin_url_open_in_browser"
|
||||||
|
result.Actions = []plugin.QueryResultAction{
|
||||||
|
{
|
||||||
|
Name: "i18n:plugin_url_open",
|
||||||
|
Icon: urlIcon,
|
||||||
|
Action: func(ctx context.Context, actionContext plugin.ActionContext) {
|
||||||
|
openErr := shell.Open(url)
|
||||||
|
if openErr != nil {
|
||||||
|
r.api.Log(ctx, "Error opening URL", openErr.Error())
|
||||||
|
} else {
|
||||||
|
util.Go(ctx, "saveRecentUrl", func() {
|
||||||
|
r.saveRecentUrl(ctx, url)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
@ -9,10 +9,11 @@
|
||||||
"ui_selection_hotkey_tips": "Hotkeys to do actions on selected text or files",
|
"ui_selection_hotkey_tips": "Hotkeys to do actions on selected text or files",
|
||||||
"ui_use_pinyin": "Use Pinyin",
|
"ui_use_pinyin": "Use Pinyin",
|
||||||
"ui_use_pinyin_tips": "When selected, Wox will convert Chinese into Pinyin",
|
"ui_use_pinyin_tips": "When selected, Wox will convert Chinese into Pinyin",
|
||||||
"ui_last_query_mode": "Last Query Mode",
|
"ui_query_mode": "Query Mode",
|
||||||
"ui_last_query_mode_tips": "Choose the default behavior when opening Wox",
|
"ui_query_mode_tips": "Choose the default behavior when opening Wox",
|
||||||
"ui_last_query_mode_preserve": "Preserve last query",
|
"ui_query_mode_preserve": "Preserve last query",
|
||||||
"ui_last_query_mode_empty": "Always start with empty query",
|
"ui_query_mode_empty": "Always start with empty query",
|
||||||
|
"ui_query_mode_mru": "Show most recently used items",
|
||||||
"ui_hide_on_lost_focus": "Hide on lost focus",
|
"ui_hide_on_lost_focus": "Hide on lost focus",
|
||||||
"ui_hide_on_lost_focus_tips": "When selected, Wox will hide when it loses focus",
|
"ui_hide_on_lost_focus_tips": "When selected, Wox will hide when it loses focus",
|
||||||
"ui_hide_on_start": "Hide on start",
|
"ui_hide_on_start": "Hide on start",
|
||||||
|
@ -386,6 +387,7 @@
|
||||||
"plugin_ai_chat_default_model_tooltip": "The default model to use for this command",
|
"plugin_ai_chat_default_model_tooltip": "The default model to use for this command",
|
||||||
"plugin_ai_chat_enable_fallback_search": "Fallback Search",
|
"plugin_ai_chat_enable_fallback_search": "Fallback Search",
|
||||||
"plugin_ai_chat_enable_fallback_search_tooltip": "When enabled, if the query result is empty, Wox will return a result of 'use AI Chat for %s'",
|
"plugin_ai_chat_enable_fallback_search_tooltip": "When enabled, if the query result is empty, Wox will return a result of 'use AI Chat for %s'",
|
||||||
|
"plugin_app_open_failed_description": "Failed to open: %s",
|
||||||
"plugin_ai_chat_fallback_search_chat_for": "Use AI Chat for %s",
|
"plugin_ai_chat_fallback_search_chat_for": "Use AI Chat for %s",
|
||||||
"plugin_ai_chat_start_chat": "Start Chat",
|
"plugin_ai_chat_start_chat": "Start Chat",
|
||||||
"plugin_ai_chat_enable_auto_focus_to_chat_input": "Auto focus to chat input when open with query hotkey",
|
"plugin_ai_chat_enable_auto_focus_to_chat_input": "Auto focus to chat input when open with query hotkey",
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
"ui_last_query_mode_tips": "Escolha o comportamento padrão ao abrir o Wox",
|
"ui_last_query_mode_tips": "Escolha o comportamento padrão ao abrir o Wox",
|
||||||
"ui_last_query_mode_preserve": "Preservar última consulta",
|
"ui_last_query_mode_preserve": "Preservar última consulta",
|
||||||
"ui_last_query_mode_empty": "Sempre começar com consulta vazia",
|
"ui_last_query_mode_empty": "Sempre começar com consulta vazia",
|
||||||
|
"ui_last_query_mode_mru": "Mostrar itens usados recentemente",
|
||||||
|
"ui_last_query_mode_preserve": "Preservar última consulta",
|
||||||
|
"ui_last_query_mode_empty": "Sempre começar com consulta vazia",
|
||||||
"ui_hide_on_lost_focus": "Ocultar na perda do foco",
|
"ui_hide_on_lost_focus": "Ocultar na perda do foco",
|
||||||
"ui_hide_on_lost_focus_tips": "Quando selecionado, o Wox será ocultado na perda do foco",
|
"ui_hide_on_lost_focus_tips": "Quando selecionado, o Wox será ocultado na perda do foco",
|
||||||
"ui_hide_on_start": "Ocultar ao iniciar",
|
"ui_hide_on_start": "Ocultar ao iniciar",
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
"ui_last_query_mode_tips": "Выберите поведение по умолчанию при открытии Wox",
|
"ui_last_query_mode_tips": "Выберите поведение по умолчанию при открытии Wox",
|
||||||
"ui_last_query_mode_preserve": "Сохранить последний запрос",
|
"ui_last_query_mode_preserve": "Сохранить последний запрос",
|
||||||
"ui_last_query_mode_empty": "Всегда начинать с пустого запроса",
|
"ui_last_query_mode_empty": "Всегда начинать с пустого запроса",
|
||||||
|
"ui_last_query_mode_mru": "Показать недавно использованные элементы",
|
||||||
|
"ui_last_query_mode_preserve": "Сохранить последний запрос",
|
||||||
|
"ui_last_query_mode_empty": "Всегда начинать с пустого запроса",
|
||||||
"ui_hide_on_lost_focus": "Скрывать при потере фокуса",
|
"ui_hide_on_lost_focus": "Скрывать при потере фокуса",
|
||||||
"ui_hide_on_lost_focus_tips": "При выборе Wox будет скрываться при потере фокуса",
|
"ui_hide_on_lost_focus_tips": "При выборе Wox будет скрываться при потере фокуса",
|
||||||
"ui_hide_on_start": "Скрывать при запуске",
|
"ui_hide_on_start": "Скрывать при запуске",
|
||||||
|
|
|
@ -9,10 +9,11 @@
|
||||||
"ui_selection_hotkey_tips": "用于在选定的文本或文件上执行操作的快捷键",
|
"ui_selection_hotkey_tips": "用于在选定的文本或文件上执行操作的快捷键",
|
||||||
"ui_use_pinyin": "使用拼音",
|
"ui_use_pinyin": "使用拼音",
|
||||||
"ui_use_pinyin_tips": "搜索时,把中文转换为拼音",
|
"ui_use_pinyin_tips": "搜索时,把中文转换为拼音",
|
||||||
"ui_last_query_mode": "上次查询模式",
|
"ui_query_mode": "查询模式",
|
||||||
"ui_last_query_mode_tips": "选择打开Wox时的默认行为",
|
"ui_query_mode_tips": "选择打开Wox时的默认行为",
|
||||||
"ui_last_query_mode_preserve": "保留上次查询",
|
"ui_query_mode_preserve": "保留上次查询",
|
||||||
"ui_last_query_mode_empty": "总是从空查询开始",
|
"ui_query_mode_empty": "总是从空查询开始",
|
||||||
|
"ui_query_mode_mru": "显示最近使用的项目",
|
||||||
"ui_hide_on_lost_focus": "失去焦点时隐藏",
|
"ui_hide_on_lost_focus": "失去焦点时隐藏",
|
||||||
"ui_hide_on_lost_focus_tips": "选中后,Wox失去焦点时将隐藏",
|
"ui_hide_on_lost_focus_tips": "选中后,Wox失去焦点时将隐藏",
|
||||||
"ui_hide_on_start": "启动时隐藏",
|
"ui_hide_on_start": "启动时隐藏",
|
||||||
|
@ -386,6 +387,11 @@
|
||||||
"plugin_ai_chat_default_model_tooltip": "用于对话的默认模型",
|
"plugin_ai_chat_default_model_tooltip": "用于对话的默认模型",
|
||||||
"plugin_ai_chat_enable_fallback_search": "回退搜索",
|
"plugin_ai_chat_enable_fallback_search": "回退搜索",
|
||||||
"plugin_ai_chat_enable_fallback_search_tooltip": "当查询结果为空时,Wox 将返回一个 '使用 AI Chat 查询 %s' 的结果",
|
"plugin_ai_chat_enable_fallback_search_tooltip": "当查询结果为空时,Wox 将返回一个 '使用 AI Chat 查询 %s' 的结果",
|
||||||
|
"plugin_app_open": "打开",
|
||||||
|
"plugin_app_open_containing_folder": "打开所在文件夹",
|
||||||
|
"plugin_app_copy_path": "复制路径",
|
||||||
|
"plugin_app_terminate": "终止进程",
|
||||||
|
"plugin_app_open_failed_description": "打开失败:%s",
|
||||||
"plugin_ai_chat_fallback_search_chat_for": "使用 AI Chat 查询 %s",
|
"plugin_ai_chat_fallback_search_chat_for": "使用 AI Chat 查询 %s",
|
||||||
"plugin_ai_chat_start_chat": "开始对话",
|
"plugin_ai_chat_start_chat": "开始对话",
|
||||||
"plugin_ai_chat_enable_auto_focus_to_chat_input": "使用查询快捷键打开时自动聚焦到对话输入框",
|
"plugin_ai_chat_enable_auto_focus_to_chat_input": "使用查询快捷键打开时自动聚焦到对话输入框",
|
||||||
|
|
|
@ -4,10 +4,13 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
"wox/common"
|
"wox/common"
|
||||||
"wox/database"
|
"wox/database"
|
||||||
"wox/util"
|
"wox/util"
|
||||||
"wox/util/autostart"
|
"wox/util/autostart"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
var managerInstance *Manager
|
var managerInstance *Manager
|
||||||
|
@ -16,6 +19,7 @@ var logger *util.Log
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
woxSetting *WoxSetting
|
woxSetting *WoxSetting
|
||||||
|
mruManager *MRUManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSettingManager() *Manager {
|
func GetSettingManager() *Manager {
|
||||||
|
@ -30,6 +34,7 @@ func GetSettingManager() *Manager {
|
||||||
store := NewWoxSettingStore(db)
|
store := NewWoxSettingStore(db)
|
||||||
managerInstance = &Manager{}
|
managerInstance = &Manager{}
|
||||||
managerInstance.woxSetting = NewWoxSetting(store)
|
managerInstance.woxSetting = NewWoxSetting(store)
|
||||||
|
managerInstance.mruManager = NewMRUManager(db)
|
||||||
})
|
})
|
||||||
return managerInstance
|
return managerInstance
|
||||||
}
|
}
|
||||||
|
@ -77,14 +82,14 @@ func (m *Manager) GetWoxSetting(ctx context.Context) *WoxSetting {
|
||||||
return m.woxSetting
|
return m.woxSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetLatestQueryHistory(ctx context.Context, limit int) []common.PlainQuery {
|
func (m *Manager) GetLatestQueryHistory(ctx context.Context, limit int) []QueryHistory {
|
||||||
histories := m.woxSetting.QueryHistories.Get()
|
histories := m.woxSetting.QueryHistories.Get()
|
||||||
|
|
||||||
// Sort by timestamp descending and limit results
|
// Sort by timestamp descending and limit results
|
||||||
var result []common.PlainQuery
|
var result []QueryHistory
|
||||||
count := 0
|
count := 0
|
||||||
for i := len(histories) - 1; i >= 0 && count < limit; i-- {
|
for i := len(histories) - 1; i >= 0 && count < limit; i-- {
|
||||||
result = append(result, histories[i].Query)
|
result = append(result, histories[i])
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,3 +142,64 @@ func (m *Manager) RemoveFavoriteResult(ctx context.Context, pluginId string, res
|
||||||
favoriteResults.Delete(resultHash)
|
favoriteResults.Delete(resultHash)
|
||||||
m.woxSetting.FavoriteResults.Set(favoriteResults)
|
m.woxSetting.FavoriteResults.Set(favoriteResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) AddQueryHistory(ctx context.Context, query common.PlainQuery) {
|
||||||
|
histories := m.woxSetting.QueryHistories.Get()
|
||||||
|
newHistory := QueryHistory{
|
||||||
|
Query: query,
|
||||||
|
Timestamp: util.GetSystemTimestamp(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicate if exists (same query text)
|
||||||
|
histories = lo.Filter(histories, func(item QueryHistory, index int) bool {
|
||||||
|
return !item.Query.IsEmpty() && item.Query.QueryText != query.QueryText
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add new history at the end
|
||||||
|
histories = append(histories, newHistory)
|
||||||
|
|
||||||
|
// Keep only the most recent 1000 entries
|
||||||
|
if len(histories) > 1000 {
|
||||||
|
histories = histories[len(histories)-1000:]
|
||||||
|
}
|
||||||
|
|
||||||
|
m.woxSetting.QueryHistories.Set(histories)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MRU related methods
|
||||||
|
|
||||||
|
func (m *Manager) AddMRUItem(ctx context.Context, item MRUItem) error {
|
||||||
|
return m.mruManager.AddMRUItem(ctx, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) GetMRUItems(ctx context.Context, limit int) ([]MRUItem, error) {
|
||||||
|
return m.mruManager.GetMRUItems(ctx, limit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) RemoveMRUItem(ctx context.Context, pluginID, title, subTitle string) error {
|
||||||
|
return m.mruManager.RemoveMRUItem(ctx, pluginID, title, subTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) CleanupOldMRUItems(ctx context.Context, keepCount int) error {
|
||||||
|
return m.mruManager.CleanupOldMRUItems(ctx, keepCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartMRUCleanup starts a background goroutine to periodically clean up old MRU items
|
||||||
|
func (m *Manager) StartMRUCleanup(ctx context.Context) {
|
||||||
|
util.Go(ctx, "MRU cleanup", func() {
|
||||||
|
ticker := time.NewTicker(24 * time.Hour) // Clean up once per day
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
// Keep only the most recent 100 MRU items
|
||||||
|
if err := m.CleanupOldMRUItems(ctx, 100); err != nil {
|
||||||
|
util.GetLogger().Error(ctx, fmt.Sprintf("failed to cleanup old MRU items: %s", err.Error()))
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
"wox/common"
|
||||||
|
"wox/database"
|
||||||
|
"wox/util"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MRUItem represents a Most Recently Used item
|
||||||
|
type MRUItem struct {
|
||||||
|
PluginID string `json:"pluginId"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
SubTitle string `json:"subTitle"`
|
||||||
|
Icon common.WoxImage `json:"icon"`
|
||||||
|
ContextData string `json:"contextData"`
|
||||||
|
LastUsed int64 `json:"lastUsed"`
|
||||||
|
UseCount int `json:"useCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MRUManager manages Most Recently Used items
|
||||||
|
type MRUManager struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMRUManager creates a new MRU manager
|
||||||
|
func NewMRUManager(db *gorm.DB) *MRUManager {
|
||||||
|
return &MRUManager{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMRUItem adds or updates an MRU item
|
||||||
|
func (m *MRUManager) AddMRUItem(ctx context.Context, item MRUItem) error {
|
||||||
|
hash := NewResultHash(item.PluginID, item.Title, item.SubTitle)
|
||||||
|
|
||||||
|
// Serialize icon to JSON
|
||||||
|
iconData, err := json.Marshal(item.Icon)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to serialize icon: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
timestamp := util.GetSystemTimestamp()
|
||||||
|
|
||||||
|
// Check if record exists
|
||||||
|
var existingRecord database.MRURecord
|
||||||
|
err = m.db.Where("hash = ?", string(hash)).First(&existingRecord).Error
|
||||||
|
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
// Create new record
|
||||||
|
record := database.MRURecord{
|
||||||
|
Hash: string(hash),
|
||||||
|
PluginID: item.PluginID,
|
||||||
|
Title: item.Title,
|
||||||
|
SubTitle: item.SubTitle,
|
||||||
|
Icon: string(iconData),
|
||||||
|
ContextData: item.ContextData,
|
||||||
|
LastUsed: timestamp,
|
||||||
|
UseCount: 1,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
return m.db.Create(&record).Error
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("failed to query MRU record: %w", err)
|
||||||
|
} else {
|
||||||
|
// Update existing record
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"last_used": timestamp,
|
||||||
|
"use_count": existingRecord.UseCount + 1,
|
||||||
|
"context_data": item.ContextData, // Update context data in case it changed
|
||||||
|
"icon": string(iconData), // Update icon in case it changed
|
||||||
|
"updated_at": now,
|
||||||
|
}
|
||||||
|
return m.db.Model(&existingRecord).Updates(updates).Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMRUItems retrieves MRU items sorted by usage
|
||||||
|
func (m *MRUManager) GetMRUItems(ctx context.Context, limit int) ([]MRUItem, error) {
|
||||||
|
var records []database.MRURecord
|
||||||
|
|
||||||
|
// Order by last_used DESC, then by use_count DESC for items with same last_used time
|
||||||
|
err := m.db.Order("last_used DESC, use_count DESC").Limit(limit).Find(&records).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to query MRU records: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]MRUItem, 0, len(records))
|
||||||
|
for _, record := range records {
|
||||||
|
// Deserialize icon
|
||||||
|
var icon common.WoxImage
|
||||||
|
if err := json.Unmarshal([]byte(record.Icon), &icon); err != nil {
|
||||||
|
util.GetLogger().Warn(ctx, fmt.Sprintf("failed to deserialize icon for MRU item %s: %s", record.Hash, err.Error()))
|
||||||
|
icon = common.WoxImage{} // Use empty icon as fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, MRUItem{
|
||||||
|
PluginID: record.PluginID,
|
||||||
|
Title: record.Title,
|
||||||
|
SubTitle: record.SubTitle,
|
||||||
|
Icon: icon,
|
||||||
|
ContextData: record.ContextData,
|
||||||
|
LastUsed: record.LastUsed,
|
||||||
|
UseCount: record.UseCount,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveMRUItem removes an MRU item by hash
|
||||||
|
func (m *MRUManager) RemoveMRUItem(ctx context.Context, pluginID, title, subTitle string) error {
|
||||||
|
hash := NewResultHash(pluginID, title, subTitle)
|
||||||
|
result := m.db.Where("hash = ?", string(hash)).Delete(&database.MRURecord{})
|
||||||
|
if result.Error != nil {
|
||||||
|
return fmt.Errorf("failed to remove MRU item: %w", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
util.GetLogger().Debug(ctx, fmt.Sprintf("removed MRU item: %s", hash))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanupOldMRUItems removes old MRU items to keep the database size manageable
|
||||||
|
func (m *MRUManager) CleanupOldMRUItems(ctx context.Context, keepCount int) error {
|
||||||
|
// Keep only the most recent keepCount items
|
||||||
|
subQuery := m.db.Model(&database.MRURecord{}).
|
||||||
|
Select("hash").
|
||||||
|
Order("last_used DESC, use_count DESC").
|
||||||
|
Limit(keepCount)
|
||||||
|
|
||||||
|
result := m.db.Where("hash NOT IN (?)", subQuery).Delete(&database.MRURecord{})
|
||||||
|
if result.Error != nil {
|
||||||
|
return fmt.Errorf("failed to cleanup old MRU items: %w", result.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.RowsAffected > 0 {
|
||||||
|
util.GetLogger().Info(ctx, fmt.Sprintf("cleaned up %d old MRU items", result.RowsAffected))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMRUCount returns the total number of MRU items
|
||||||
|
func (m *MRUManager) GetMRUCount(ctx context.Context) (int64, error) {
|
||||||
|
var count int64
|
||||||
|
err := m.db.Model(&database.MRURecord{}).Count(&count).Error
|
||||||
|
return count, err
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ type WoxSetting struct {
|
||||||
LangCode *WoxSettingValue[i18n.LangCode]
|
LangCode *WoxSettingValue[i18n.LangCode]
|
||||||
QueryHotkeys *PlatformValue[[]QueryHotkey]
|
QueryHotkeys *PlatformValue[[]QueryHotkey]
|
||||||
QueryShortcuts *WoxSettingValue[[]QueryShortcut]
|
QueryShortcuts *WoxSettingValue[[]QueryShortcut]
|
||||||
LastQueryMode *WoxSettingValue[LastQueryMode]
|
QueryMode *WoxSettingValue[QueryMode]
|
||||||
ShowPosition *WoxSettingValue[PositionType]
|
ShowPosition *WoxSettingValue[PositionType]
|
||||||
AIProviders *WoxSettingValue[[]AIProvider]
|
AIProviders *WoxSettingValue[[]AIProvider]
|
||||||
EnableAutoBackup *WoxSettingValue[bool]
|
EnableAutoBackup *WoxSettingValue[bool]
|
||||||
|
@ -49,7 +49,7 @@ type WoxSetting struct {
|
||||||
ActionedResults *WoxSettingValue[*util.HashMap[ResultHash, []ActionedResult]]
|
ActionedResults *WoxSettingValue[*util.HashMap[ResultHash, []ActionedResult]]
|
||||||
}
|
}
|
||||||
|
|
||||||
type LastQueryMode = string
|
type QueryMode = string
|
||||||
|
|
||||||
type PositionType string
|
type PositionType string
|
||||||
|
|
||||||
|
@ -60,8 +60,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LastQueryModePreserve LastQueryMode = "preserve" // preserve last query and select all for quick modify
|
QueryModePreserve QueryMode = "preserve" // preserve last query and select all for quick modify
|
||||||
LastQueryModeEmpty LastQueryMode = "empty" // empty last query
|
QueryModeEmpty QueryMode = "empty" // empty last query
|
||||||
|
QueryModeMRU QueryMode = "mru" // show MRU (Most Recently Used) list
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -134,7 +135,7 @@ func NewWoxSetting(store *WoxSettingStore) *WoxSetting {
|
||||||
LangCode: NewWoxSettingValueWithValidator(store, "LangCode", defaultLangCode, func(code i18n.LangCode) bool {
|
LangCode: NewWoxSettingValueWithValidator(store, "LangCode", defaultLangCode, func(code i18n.LangCode) bool {
|
||||||
return i18n.IsSupportedLangCode(string(code))
|
return i18n.IsSupportedLangCode(string(code))
|
||||||
}),
|
}),
|
||||||
LastQueryMode: NewWoxSettingValue(store, "LastQueryMode", LastQueryModeEmpty),
|
QueryMode: NewWoxSettingValue(store, "QueryMode", QueryModeEmpty),
|
||||||
ShowPosition: NewWoxSettingValue(store, "ShowPosition", PositionTypeMouseScreen),
|
ShowPosition: NewWoxSettingValue(store, "ShowPosition", PositionTypeMouseScreen),
|
||||||
AppWidth: NewWoxSettingValue(store, "AppWidth", 800),
|
AppWidth: NewWoxSettingValue(store, "AppWidth", 800),
|
||||||
MaxResultCount: NewWoxSettingValue(store, "MaxResultCount", 10),
|
MaxResultCount: NewWoxSettingValue(store, "MaxResultCount", 10),
|
||||||
|
|
|
@ -17,7 +17,7 @@ type WoxSettingDto struct {
|
||||||
LangCode i18n.LangCode
|
LangCode i18n.LangCode
|
||||||
QueryHotkeys []setting.QueryHotkey
|
QueryHotkeys []setting.QueryHotkey
|
||||||
QueryShortcuts []setting.QueryShortcut
|
QueryShortcuts []setting.QueryShortcut
|
||||||
LastQueryMode setting.LastQueryMode
|
QueryMode setting.QueryMode
|
||||||
AIProviders []setting.AIProvider
|
AIProviders []setting.AIProvider
|
||||||
HttpProxyEnabled bool
|
HttpProxyEnabled bool
|
||||||
HttpProxyUrl string
|
HttpProxyUrl string
|
||||||
|
|
|
@ -88,6 +88,7 @@ var routers = map[string]func(w http.ResponseWriter, r *http.Request){
|
||||||
"/hotkey/available": handleHotkeyAvailable,
|
"/hotkey/available": handleHotkeyAvailable,
|
||||||
"/query/icon": handleQueryIcon,
|
"/query/icon": handleQueryIcon,
|
||||||
"/query/ratio": handleQueryRatio,
|
"/query/ratio": handleQueryRatio,
|
||||||
|
"/query/mru": handleQueryMRU,
|
||||||
"/deeplink": handleDeeplink,
|
"/deeplink": handleDeeplink,
|
||||||
"/version": handleVersion,
|
"/version": handleVersion,
|
||||||
}
|
}
|
||||||
|
@ -484,7 +485,7 @@ func handleSettingWox(w http.ResponseWriter, r *http.Request) {
|
||||||
settingDto.LangCode = woxSetting.LangCode.Get()
|
settingDto.LangCode = woxSetting.LangCode.Get()
|
||||||
settingDto.QueryHotkeys = woxSetting.QueryHotkeys.Get()
|
settingDto.QueryHotkeys = woxSetting.QueryHotkeys.Get()
|
||||||
settingDto.QueryShortcuts = woxSetting.QueryShortcuts.Get()
|
settingDto.QueryShortcuts = woxSetting.QueryShortcuts.Get()
|
||||||
settingDto.LastQueryMode = woxSetting.LastQueryMode.Get()
|
settingDto.QueryMode = woxSetting.QueryMode.Get()
|
||||||
settingDto.AIProviders = woxSetting.AIProviders.Get()
|
settingDto.AIProviders = woxSetting.AIProviders.Get()
|
||||||
settingDto.HttpProxyEnabled = woxSetting.HttpProxyEnabled.Get()
|
settingDto.HttpProxyEnabled = woxSetting.HttpProxyEnabled.Get()
|
||||||
settingDto.HttpProxyUrl = woxSetting.HttpProxyUrl.Get()
|
settingDto.HttpProxyUrl = woxSetting.HttpProxyUrl.Get()
|
||||||
|
@ -537,8 +538,8 @@ func handleSettingWoxUpdate(w http.ResponseWriter, r *http.Request) {
|
||||||
woxSetting.ShowTray.Set(kv.Value.(bool))
|
woxSetting.ShowTray.Set(kv.Value.(bool))
|
||||||
case "LangCode":
|
case "LangCode":
|
||||||
woxSetting.LangCode.Set(i18n.LangCode(kv.Value.(string)))
|
woxSetting.LangCode.Set(i18n.LangCode(kv.Value.(string)))
|
||||||
case "LastQueryMode":
|
case "QueryMode":
|
||||||
woxSetting.LastQueryMode.Set(setting.LastQueryMode(kv.Value.(string)))
|
woxSetting.QueryMode.Set(setting.QueryMode(kv.Value.(string)))
|
||||||
case "ShowPosition":
|
case "ShowPosition":
|
||||||
woxSetting.ShowPosition.Set(setting.PositionType(kv.Value.(string)))
|
woxSetting.ShowPosition.Set(setting.PositionType(kv.Value.(string)))
|
||||||
case "EnableAutoBackup":
|
case "EnableAutoBackup":
|
||||||
|
@ -1155,3 +1156,23 @@ func handlePluginDetail(w http.ResponseWriter, r *http.Request) {
|
||||||
func handleVersion(w http.ResponseWriter, r *http.Request) {
|
func handleVersion(w http.ResponseWriter, r *http.Request) {
|
||||||
writeSuccessResponse(w, updater.CURRENT_VERSION)
|
writeSuccessResponse(w, updater.CURRENT_VERSION)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleQueryMRU(w http.ResponseWriter, r *http.Request) {
|
||||||
|
body, readErr := io.ReadAll(r.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
writeErrorResponse(w, fmt.Sprintf("failed to read request body: %s", readErr.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var ctx context.Context
|
||||||
|
traceIdResult := gjson.GetBytes(body, "traceId")
|
||||||
|
if traceIdResult.Exists() && traceIdResult.String() != "" {
|
||||||
|
ctx = util.NewTraceContextWith(traceIdResult.String())
|
||||||
|
} else {
|
||||||
|
ctx = util.NewTraceContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
mruResults := plugin.GetPluginManager().QueryMRU(ctx)
|
||||||
|
logger.Info(ctx, fmt.Sprintf("found %d MRU results", len(mruResults)))
|
||||||
|
writeSuccessResponse(w, mruResults)
|
||||||
|
}
|
||||||
|
|
|
@ -203,7 +203,7 @@ func getShowAppParams(ctx context.Context, showContext common.ShowContext) map[s
|
||||||
"AutoFocusToChatInput": showContext.AutoFocusToChatInput,
|
"AutoFocusToChatInput": showContext.AutoFocusToChatInput,
|
||||||
"Position": position,
|
"Position": position,
|
||||||
"QueryHistories": setting.GetSettingManager().GetLatestQueryHistory(ctx, 10),
|
"QueryHistories": setting.GetSettingManager().GetLatestQueryHistory(ctx, 10),
|
||||||
"LastQueryMode": woxSetting.LastQueryMode.Get(),
|
"QueryMode": woxSetting.QueryMode.Get(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -183,6 +183,14 @@ class WoxApi {
|
||||||
return await WoxHttpUtil.instance.postData<List<DoctorCheckResult>>("/doctor/check", null);
|
return await WoxHttpUtil.instance.postData<List<DoctorCheckResult>>("/doctor/check", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<WoxQueryResult>> queryMRU(String traceId) async {
|
||||||
|
final response = await WoxHttpUtil.instance.postData("/query/mru", {"traceId": traceId});
|
||||||
|
if (response is List) {
|
||||||
|
return response.map((item) => WoxQueryResult.fromJson(item)).toList();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> getUserDataLocation() async {
|
Future<String> getUserDataLocation() async {
|
||||||
return await WoxHttpUtil.instance.postData("/setting/userdata/location", null);
|
return await WoxHttpUtil.instance.postData("/setting/userdata/location", null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,14 @@ import 'package:wox/entity/wox_hotkey.dart';
|
||||||
import 'package:wox/entity/wox_image.dart';
|
import 'package:wox/entity/wox_image.dart';
|
||||||
import 'package:wox/entity/wox_preview.dart';
|
import 'package:wox/entity/wox_preview.dart';
|
||||||
import 'package:wox/entity/wox_query.dart';
|
import 'package:wox/entity/wox_query.dart';
|
||||||
|
import 'package:wox/enums/wox_query_mode_enum.dart';
|
||||||
import 'package:wox/entity/wox_setting.dart';
|
import 'package:wox/entity/wox_setting.dart';
|
||||||
import 'package:wox/entity/wox_theme.dart';
|
import 'package:wox/entity/wox_theme.dart';
|
||||||
import 'package:wox/entity/wox_toolbar.dart';
|
import 'package:wox/entity/wox_toolbar.dart';
|
||||||
import 'package:wox/entity/wox_websocket_msg.dart';
|
import 'package:wox/entity/wox_websocket_msg.dart';
|
||||||
import 'package:wox/enums/wox_direction_enum.dart';
|
import 'package:wox/enums/wox_direction_enum.dart';
|
||||||
import 'package:wox/enums/wox_image_type_enum.dart';
|
import 'package:wox/enums/wox_image_type_enum.dart';
|
||||||
import 'package:wox/enums/wox_last_query_mode_enum.dart';
|
|
||||||
import 'package:wox/enums/wox_msg_method_enum.dart';
|
import 'package:wox/enums/wox_msg_method_enum.dart';
|
||||||
import 'package:wox/enums/wox_msg_type_enum.dart';
|
import 'package:wox/enums/wox_msg_type_enum.dart';
|
||||||
import 'package:wox/enums/wox_position_type_enum.dart';
|
import 'package:wox/enums/wox_position_type_enum.dart';
|
||||||
|
@ -78,7 +79,7 @@ class WoxLauncherController extends GetxController {
|
||||||
var currentQueryHistoryIndex = 0; // query history index, used to navigate query history
|
var currentQueryHistoryIndex = 0; // query history index, used to navigate query history
|
||||||
|
|
||||||
var refreshCounter = 0;
|
var refreshCounter = 0;
|
||||||
var lastQueryMode = WoxLastQueryModeEnum.WOX_LAST_QUERY_MODE_PRESERVE.code;
|
var lastQueryMode = WoxQueryModeEnum.WOX_QUERY_MODE_PRESERVE.code;
|
||||||
final isInSettingView = false.obs;
|
final isInSettingView = false.obs;
|
||||||
var positionBeforeOpenSetting = const Offset(0, 0);
|
var positionBeforeOpenSetting = const Offset(0, 0);
|
||||||
|
|
||||||
|
@ -148,7 +149,6 @@ class WoxLauncherController extends GetxController {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//ignore the results if the query id is not matched
|
|
||||||
if (currentQuery.value.queryId != receivedResults.first.queryId) {
|
if (currentQuery.value.queryId != receivedResults.first.queryId) {
|
||||||
Logger.instance.error(traceId, "query id is not matched, ignore the results");
|
Logger.instance.error(traceId, "query id is not matched, ignore the results");
|
||||||
return;
|
return;
|
||||||
|
@ -215,7 +215,7 @@ class WoxLauncherController extends GetxController {
|
||||||
Future<void> showApp(String traceId, ShowAppParams params) async {
|
Future<void> showApp(String traceId, ShowAppParams params) async {
|
||||||
if (currentQuery.value.queryType == WoxQueryTypeEnum.WOX_QUERY_TYPE_INPUT.code) {
|
if (currentQuery.value.queryType == WoxQueryTypeEnum.WOX_QUERY_TYPE_INPUT.code) {
|
||||||
canArrowUpHistory = true;
|
canArrowUpHistory = true;
|
||||||
if (lastQueryMode == WoxLastQueryModeEnum.WOX_LAST_QUERY_MODE_PRESERVE.code) {
|
if (lastQueryMode == WoxQueryModeEnum.WOX_QUERY_MODE_PRESERVE.code) {
|
||||||
//skip the first one, because it's the current query
|
//skip the first one, because it's the current query
|
||||||
currentQueryHistoryIndex = 0;
|
currentQueryHistoryIndex = 0;
|
||||||
} else {
|
} else {
|
||||||
|
@ -225,7 +225,15 @@ class WoxLauncherController extends GetxController {
|
||||||
|
|
||||||
// update some properties to latest for later use
|
// update some properties to latest for later use
|
||||||
latestQueryHistories.assignAll(params.queryHistories);
|
latestQueryHistories.assignAll(params.queryHistories);
|
||||||
lastQueryMode = params.lastQueryMode;
|
lastQueryMode = params.queryMode;
|
||||||
|
|
||||||
|
// Handle MRU mode
|
||||||
|
if (lastQueryMode == WoxQueryModeEnum.WOX_QUERY_MODE_MRU.code) {
|
||||||
|
// Clear current query and show MRU results
|
||||||
|
currentQuery.value = PlainQuery.emptyInput();
|
||||||
|
queryBoxTextFieldController.clear();
|
||||||
|
queryMRU(traceId);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle different position types
|
// Handle different position types
|
||||||
// on linux, we need to show first and then set position or center it
|
// on linux, we need to show first and then set position or center it
|
||||||
|
@ -247,8 +255,10 @@ class WoxLauncherController extends GetxController {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> hideApp(String traceId) async {
|
Future<void> hideApp(String traceId) async {
|
||||||
//clear query box text if query type is selection or last query mode is empty
|
//clear query box text if query type is selection or last query mode is empty or MRU
|
||||||
if (currentQuery.value.queryType == WoxQueryTypeEnum.WOX_QUERY_TYPE_SELECTION.code || lastQueryMode == WoxLastQueryModeEnum.WOX_LAST_QUERY_MODE_EMPTY.code) {
|
if (currentQuery.value.queryType == WoxQueryTypeEnum.WOX_QUERY_TYPE_SELECTION.code ||
|
||||||
|
lastQueryMode == WoxQueryModeEnum.WOX_QUERY_MODE_EMPTY.code ||
|
||||||
|
lastQueryMode == WoxQueryModeEnum.WOX_QUERY_MODE_MRU.code) {
|
||||||
currentQuery.value = PlainQuery.emptyInput();
|
currentQuery.value = PlainQuery.emptyInput();
|
||||||
queryBoxTextFieldController.clear();
|
queryBoxTextFieldController.clear();
|
||||||
hideActionPanel(traceId);
|
hideActionPanel(traceId);
|
||||||
|
@ -439,6 +449,24 @@ class WoxLauncherController extends GetxController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> queryMRU(String traceId) async {
|
||||||
|
clearQueryResults(traceId);
|
||||||
|
|
||||||
|
var queryId = const UuidV4().generate();
|
||||||
|
currentQuery.value = PlainQuery.emptyInput();
|
||||||
|
currentQuery.value.queryId = queryId;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final results = await WoxApi.instance.queryMRU(traceId);
|
||||||
|
for (var result in results) {
|
||||||
|
result.queryId = queryId;
|
||||||
|
}
|
||||||
|
onReceivedQueryResults(traceId, results);
|
||||||
|
} catch (e) {
|
||||||
|
Logger.instance.error(traceId, "Failed to query MRU: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void onQueryChanged(String traceId, PlainQuery query, String changeReason, {bool moveCursorToEnd = false}) {
|
void onQueryChanged(String traceId, PlainQuery query, String changeReason, {bool moveCursorToEnd = false}) {
|
||||||
Logger.instance.debug(traceId, "query changed: ${query.queryText}, reason: $changeReason");
|
Logger.instance.debug(traceId, "query changed: ${query.queryText}, reason: $changeReason");
|
||||||
|
|
||||||
|
@ -469,7 +497,12 @@ class WoxLauncherController extends GetxController {
|
||||||
updateResultPreviewWidthRatioOnQueryChanged(traceId, query);
|
updateResultPreviewWidthRatioOnQueryChanged(traceId, query);
|
||||||
updateToolbarOnQueryChanged(traceId, query);
|
updateToolbarOnQueryChanged(traceId, query);
|
||||||
if (query.isEmpty) {
|
if (query.isEmpty) {
|
||||||
clearQueryResults(traceId);
|
// Check if we should show MRU results when query is empty
|
||||||
|
if (lastQueryMode == WoxQueryModeEnum.WOX_QUERY_MODE_MRU.code) {
|
||||||
|
queryMRU(traceId);
|
||||||
|
} else {
|
||||||
|
clearQueryResults(traceId);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -854,7 +887,7 @@ class WoxLauncherController extends GetxController {
|
||||||
traceId,
|
traceId,
|
||||||
ShowAppParams(
|
ShowAppParams(
|
||||||
queryHistories: latestQueryHistories,
|
queryHistories: latestQueryHistories,
|
||||||
lastQueryMode: lastQueryMode,
|
queryMode: lastQueryMode,
|
||||||
selectAll: true,
|
selectAll: true,
|
||||||
position: Position(
|
position: Position(
|
||||||
type: WoxPositionTypeEnum.POSITION_TYPE_LAST_LOCATION.code,
|
type: WoxPositionTypeEnum.POSITION_TYPE_LAST_LOCATION.code,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:wox/entity/wox_image.dart';
|
import 'package:wox/entity/wox_image.dart';
|
||||||
import 'package:wox/entity/wox_list_item.dart';
|
import 'package:wox/entity/wox_list_item.dart';
|
||||||
import 'package:wox/entity/wox_preview.dart';
|
import 'package:wox/entity/wox_preview.dart';
|
||||||
import 'package:wox/enums/wox_last_query_mode_enum.dart';
|
import 'package:wox/enums/wox_query_mode_enum.dart';
|
||||||
import 'package:wox/enums/wox_position_type_enum.dart';
|
import 'package:wox/enums/wox_position_type_enum.dart';
|
||||||
import 'package:wox/enums/wox_query_type_enum.dart';
|
import 'package:wox/enums/wox_query_type_enum.dart';
|
||||||
import 'package:wox/enums/wox_selection_type_enum.dart';
|
import 'package:wox/enums/wox_selection_type_enum.dart';
|
||||||
|
@ -262,10 +262,10 @@ class ShowAppParams {
|
||||||
late bool selectAll;
|
late bool selectAll;
|
||||||
late Position position;
|
late Position position;
|
||||||
late List<QueryHistory> queryHistories;
|
late List<QueryHistory> queryHistories;
|
||||||
late WoxLastQueryMode lastQueryMode;
|
late WoxQueryMode queryMode;
|
||||||
late bool autoFocusToChatInput;
|
late bool autoFocusToChatInput;
|
||||||
|
|
||||||
ShowAppParams({required this.selectAll, required this.position, required this.queryHistories, required this.lastQueryMode, this.autoFocusToChatInput = false});
|
ShowAppParams({required this.selectAll, required this.position, required this.queryHistories, required this.queryMode, this.autoFocusToChatInput = false});
|
||||||
|
|
||||||
ShowAppParams.fromJson(Map<String, dynamic> json) {
|
ShowAppParams.fromJson(Map<String, dynamic> json) {
|
||||||
selectAll = json['SelectAll'];
|
selectAll = json['SelectAll'];
|
||||||
|
@ -274,13 +274,10 @@ class ShowAppParams {
|
||||||
}
|
}
|
||||||
queryHistories = <QueryHistory>[];
|
queryHistories = <QueryHistory>[];
|
||||||
if (json['QueryHistories'] != null) {
|
if (json['QueryHistories'] != null) {
|
||||||
json['QueryHistories'].forEach((v) {
|
final List<dynamic> histories = json['QueryHistories'];
|
||||||
queryHistories.add(QueryHistory.fromJson(v));
|
queryHistories = histories.map((v) => QueryHistory.fromJson(v)).toList();
|
||||||
});
|
|
||||||
} else {
|
|
||||||
queryHistories = <QueryHistory>[];
|
|
||||||
}
|
}
|
||||||
lastQueryMode = json['LastQueryMode'];
|
queryMode = json['QueryMode'] ?? 'empty';
|
||||||
autoFocusToChatInput = json['AutoFocusToChatInput'] ?? false;
|
autoFocusToChatInput = json['AutoFocusToChatInput'] ?? false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ class WoxSetting {
|
||||||
late String langCode;
|
late String langCode;
|
||||||
late List<QueryHotkey> queryHotkeys;
|
late List<QueryHotkey> queryHotkeys;
|
||||||
late List<QueryShortcut> queryShortcuts;
|
late List<QueryShortcut> queryShortcuts;
|
||||||
late String lastQueryMode;
|
late String queryMode;
|
||||||
late String showPosition;
|
late String showPosition;
|
||||||
late List<AIProvider> aiProviders;
|
late List<AIProvider> aiProviders;
|
||||||
late int appWidth;
|
late int appWidth;
|
||||||
|
@ -37,7 +37,7 @@ class WoxSetting {
|
||||||
required this.langCode,
|
required this.langCode,
|
||||||
required this.queryHotkeys,
|
required this.queryHotkeys,
|
||||||
required this.queryShortcuts,
|
required this.queryShortcuts,
|
||||||
required this.lastQueryMode,
|
required this.queryMode,
|
||||||
required this.showPosition,
|
required this.showPosition,
|
||||||
required this.aiProviders,
|
required this.aiProviders,
|
||||||
required this.appWidth,
|
required this.appWidth,
|
||||||
|
@ -81,7 +81,7 @@ class WoxSetting {
|
||||||
queryShortcuts = <QueryShortcut>[];
|
queryShortcuts = <QueryShortcut>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
lastQueryMode = json['LastQueryMode'];
|
queryMode = json['QueryMode'] ?? 'empty';
|
||||||
|
|
||||||
if (json['AIProviders'] != null) {
|
if (json['AIProviders'] != null) {
|
||||||
aiProviders = <AIProvider>[];
|
aiProviders = <AIProvider>[];
|
||||||
|
@ -116,7 +116,7 @@ class WoxSetting {
|
||||||
data['LangCode'] = langCode;
|
data['LangCode'] = langCode;
|
||||||
data['QueryHotkeys'] = queryHotkeys;
|
data['QueryHotkeys'] = queryHotkeys;
|
||||||
data['QueryShortcuts'] = queryShortcuts;
|
data['QueryShortcuts'] = queryShortcuts;
|
||||||
data['LastQueryMode'] = lastQueryMode;
|
data['QueryMode'] = queryMode;
|
||||||
data['ShowPosition'] = showPosition;
|
data['ShowPosition'] = showPosition;
|
||||||
data['AIProviders'] = aiProviders;
|
data['AIProviders'] = aiProviders;
|
||||||
data['AppWidth'] = appWidth;
|
data['AppWidth'] = appWidth;
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
typedef WoxLastQueryMode = String;
|
|
||||||
|
|
||||||
enum WoxLastQueryModeEnum {
|
|
||||||
WOX_LAST_QUERY_MODE_PRESERVE("preserve", "preserve"),
|
|
||||||
WOX_LAST_QUERY_MODE_EMPTY("empty", "empty");
|
|
||||||
|
|
||||||
final String code;
|
|
||||||
final String value;
|
|
||||||
|
|
||||||
const WoxLastQueryModeEnum(this.code, this.value);
|
|
||||||
|
|
||||||
static String getValue(String code) => WoxLastQueryModeEnum.values.firstWhere((activity) => activity.code == code).value;
|
|
||||||
}
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
typedef WoxQueryMode = String;
|
||||||
|
|
||||||
|
enum WoxQueryModeEnum {
|
||||||
|
WOX_QUERY_MODE_PRESERVE("preserve", "preserve"),
|
||||||
|
WOX_QUERY_MODE_EMPTY("empty", "empty"),
|
||||||
|
WOX_QUERY_MODE_MRU("mru", "mru");
|
||||||
|
|
||||||
|
final String code;
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
const WoxQueryModeEnum(this.code, this.value);
|
||||||
|
|
||||||
|
static String getValue(String code) => WoxQueryModeEnum.values.firstWhere((activity) => activity.code == code).value;
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import 'package:wox/controllers/wox_launcher_controller.dart';
|
||||||
import 'package:wox/entity/setting/wox_plugin_setting_table.dart';
|
import 'package:wox/entity/setting/wox_plugin_setting_table.dart';
|
||||||
import 'package:wox/entity/wox_hotkey.dart';
|
import 'package:wox/entity/wox_hotkey.dart';
|
||||||
import 'package:wox/entity/wox_lang.dart';
|
import 'package:wox/entity/wox_lang.dart';
|
||||||
import 'package:wox/enums/wox_last_query_mode_enum.dart';
|
import 'package:wox/enums/wox_query_mode_enum.dart';
|
||||||
import 'package:wox/modules/setting/views/wox_setting_base.dart';
|
import 'package:wox/modules/setting/views/wox_setting_base.dart';
|
||||||
|
|
||||||
class WoxSettingGeneralView extends WoxSettingBaseView {
|
class WoxSettingGeneralView extends WoxSettingBaseView {
|
||||||
|
@ -115,26 +115,30 @@ class WoxSettingGeneralView extends WoxSettingBaseView {
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
formField(
|
formField(
|
||||||
label: controller.tr("ui_last_query_mode"),
|
label: controller.tr("ui_query_mode"),
|
||||||
tips: controller.tr("ui_last_query_mode_tips"),
|
tips: controller.tr("ui_query_mode_tips"),
|
||||||
child: Obx(() {
|
child: Obx(() {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: 250,
|
width: 250,
|
||||||
child: ComboBox<String>(
|
child: ComboBox<String>(
|
||||||
items: [
|
items: [
|
||||||
ComboBoxItem(
|
ComboBoxItem(
|
||||||
value: WoxLastQueryModeEnum.WOX_LAST_QUERY_MODE_PRESERVE.code,
|
value: WoxQueryModeEnum.WOX_QUERY_MODE_PRESERVE.code,
|
||||||
child: Text(controller.tr("ui_last_query_mode_preserve")),
|
child: Text(controller.tr("ui_query_mode_preserve")),
|
||||||
),
|
),
|
||||||
ComboBoxItem(
|
ComboBoxItem(
|
||||||
value: WoxLastQueryModeEnum.WOX_LAST_QUERY_MODE_EMPTY.code,
|
value: WoxQueryModeEnum.WOX_QUERY_MODE_EMPTY.code,
|
||||||
child: Text(controller.tr("ui_last_query_mode_empty")),
|
child: Text(controller.tr("ui_query_mode_empty")),
|
||||||
|
),
|
||||||
|
ComboBoxItem(
|
||||||
|
value: WoxQueryModeEnum.WOX_QUERY_MODE_MRU.code,
|
||||||
|
child: Text(controller.tr("ui_query_mode_mru")),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
value: controller.woxSetting.value.lastQueryMode,
|
value: controller.woxSetting.value.queryMode,
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
if (v != null) {
|
if (v != null) {
|
||||||
controller.updateConfig("LastQueryMode", v);
|
controller.updateConfig("QueryMode", v);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in New Issue