feat: PluginPage iframe bridge + CallPluginFunction binding
- PluginPage.svelte: bidirectional postMessage bridge with iframe - Handles: ready, get-events, create-event, update-event, delete-event - Queues messages until iframe is ready - Exports handleDrop() for drag-and-drop from parent - CallPluginFunction binding: calls arbitrary Lua functions on active plugins - Supports dotted paths: 'calendar.create_event' → _G.calendar.create_event - JSON params → Lua table conversion - LuaVM: added DoString(), LState(), VM() public methods - Plugin: added VM() getter for external access
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"verstak/internal/core/config"
|
||||
|
||||
@@ -202,16 +204,80 @@ func (a *App) ListSystemViewsWithPlugins() []SystemViewDTO {
|
||||
return base
|
||||
}
|
||||
|
||||
// CallPluginAction invokes a named Lua hook on a specific plugin.
|
||||
func (a *App) CallPluginAction(pluginName, action string, paramsJSON string) (string, error) {
|
||||
// CallPluginFunction calls a global Lua function on an active plugin.
|
||||
// The funcName can use dots: "calendar.create_event" → _G.calendar.create_event
|
||||
// Returns JSON string or error.
|
||||
func (a *App) CallPluginFunction(pluginName, funcName string, paramsJSON string) (string, error) {
|
||||
if a.plugins == nil {
|
||||
return "", fmt.Errorf("plugin manager not ready")
|
||||
}
|
||||
result, err := a.plugins.CallPluginHook(pluginName, action, lua.LString(paramsJSON))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("plugin call %s: %w", action, err)
|
||||
for _, p := range a.plugins.Plugins() {
|
||||
if p.Meta.Name != pluginName || !p.Active {
|
||||
continue
|
||||
}
|
||||
vm := p.VM()
|
||||
if vm == nil {
|
||||
continue
|
||||
}
|
||||
// Resolve dotted path: "calendar.create_event" → _G.calendar.create_event
|
||||
parts := strings.SplitN(funcName, ".", 2)
|
||||
var luaFn string
|
||||
if len(parts) == 2 {
|
||||
luaFn = fmt.Sprintf("_G.%s['%s']", parts[0], parts[1])
|
||||
} else {
|
||||
luaFn = fmt.Sprintf("_G['%s']", funcName)
|
||||
}
|
||||
|
||||
// Parse params
|
||||
var params interface{}
|
||||
if paramsJSON != "" && paramsJSON != "{}" {
|
||||
if err := json.Unmarshal([]byte(paramsJSON), ¶ms); err != nil {
|
||||
params = paramsJSON
|
||||
}
|
||||
}
|
||||
|
||||
// Convert params to Lua value
|
||||
var luaArg lua.LValue
|
||||
switch v := params.(type) {
|
||||
case nil:
|
||||
luaArg = lua.LNil
|
||||
case string:
|
||||
luaArg = lua.LString(v)
|
||||
case float64:
|
||||
luaArg = lua.LNumber(v)
|
||||
case bool:
|
||||
luaArg = lua.LBool(v)
|
||||
case map[string]interface{}:
|
||||
tbl := vm.LState().NewTable()
|
||||
for key, val := range v {
|
||||
switch sv := val.(type) {
|
||||
case string:
|
||||
tbl.RawSetString(key, lua.LString(sv))
|
||||
case float64:
|
||||
tbl.RawSetString(key, lua.LNumber(sv))
|
||||
case bool:
|
||||
tbl.RawSetString(key, lua.LBool(sv))
|
||||
}
|
||||
}
|
||||
luaArg = tbl
|
||||
default:
|
||||
luaArg = lua.LString(paramsJSON)
|
||||
}
|
||||
|
||||
var script string
|
||||
if luaArg == lua.LNil {
|
||||
script = fmt.Sprintf("return %s()", luaFn)
|
||||
} else {
|
||||
script = fmt.Sprintf("return %s(%s)", luaFn, luaArg.String())
|
||||
}
|
||||
|
||||
result, err := vm.DoString(script)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("call %s: %w", funcName, err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
return result.String(), nil
|
||||
return "", fmt.Errorf("plugin %q not active or not found", pluginName)
|
||||
}
|
||||
|
||||
// ReloadPlugins re-scans the plugins directory and re-initializes runtimes.
|
||||
|
||||
Reference in New Issue
Block a user