fix(plugins): JSON-serialize CallFunctionJSON return values + backward compat Lua args
Root cause: CallFunctionJSON used .String() on Lua return values, which for tables produces 'table: 0x...' — not valid JSON. Frontend does JSON.parse() on the result and silently caught the parse error. Fix: - runtime.go: convert Lua return value to JSON via luaValueToGo + json.Marshal so tables become proper JSON arrays/objects - main.lua: add backward compat in get_events() and update_event() to accept both positional args (start, end) and table params - CalendarPluginPage.svelte: show errors in UI instead of silent catch; restructure template to always show iframe + error overlay
This commit is contained in:
parent
fddbd3a98a
commit
f769daa617
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -19,8 +19,8 @@
|
||||||
background: #13131f;
|
background: #13131f;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script type="module" crossorigin src="/assets/main-Ch0NvF0Q.js"></script>
|
<script type="module" crossorigin src="/assets/main-BZoJ6Hxg.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/main-BdKe9T_7.css">
|
<link rel="stylesheet" crossorigin href="/assets/main-B1PBee3I.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
|
|
||||||
|
|
@ -143,9 +143,13 @@ end
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
-- Get events within a date range (inclusive)
|
-- Get events within a date range (inclusive)
|
||||||
function M.get_events(params)
|
function M.get_events(params, end_date)
|
||||||
|
-- Backward compat: support positional (start, end) and table {start=, end=}
|
||||||
|
if type(params) == "string" then
|
||||||
|
return M.get_events{ start_date = params, ["end"] = end_date or params }
|
||||||
|
end
|
||||||
local start_date = params.start_date or params.start
|
local start_date = params.start_date or params.start
|
||||||
local end_date = params.end_date or params["end"] or params.end_date
|
local end_date = params["end"] or params.end_date or params.end_date
|
||||||
if not start_date then error("start_date required") end
|
if not start_date then error("start_date required") end
|
||||||
if not end_date then end_date = start_date end
|
if not end_date then end_date = start_date end
|
||||||
return verstak.db.query(
|
return verstak.db.query(
|
||||||
|
|
@ -244,7 +248,15 @@ function M.create_event(opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update an event (partial fields)
|
-- Update an event (partial fields)
|
||||||
function M.update_event(params)
|
function M.update_event(params, fields)
|
||||||
|
-- Backward compat: support positional (id, fields) and table { id = ..., ... }
|
||||||
|
if type(params) == "string" then
|
||||||
|
local t = { id = params }
|
||||||
|
if fields then
|
||||||
|
for k, v in pairs(fields) do t[k] = v end
|
||||||
|
end
|
||||||
|
return M.update_event(t)
|
||||||
|
end
|
||||||
local id = params.id
|
local id = params.id
|
||||||
if not id then error("event id required") end
|
if not id then error("event id required") end
|
||||||
local old = verstak.db.query_row("SELECT * FROM events WHERE id = ?", id)
|
local old = verstak.db.query_row("SELECT * FROM events WHERE id = ?", id)
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[CalendarPluginPage] loadCalendarData:', e)
|
console.error('[CalendarPluginPage] loadCalendarData:', e)
|
||||||
|
error = String(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,21 +207,24 @@
|
||||||
<div class="plugin-page">
|
<div class="plugin-page">
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<p class="loading">Загрузка…</p>
|
<p class="loading">Загрузка…</p>
|
||||||
{:else if error}
|
{:else if !htmlPanel}
|
||||||
<p class="error">⚠ {error}</p>
|
|
||||||
{:else if htmlPanel}
|
|
||||||
<iframe
|
|
||||||
bind:this={iframeEl}
|
|
||||||
class="plugin-frame"
|
|
||||||
srcdoc={htmlPanel}
|
|
||||||
sandbox="allow-scripts allow-same-origin"
|
|
||||||
title="{pluginName} panel"
|
|
||||||
></iframe>
|
|
||||||
{:else}
|
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<p>Плагин «{pluginName}» активен, но HTML-панель не настроена.</p>
|
<p>Плагин «{pluginName}» активен, но HTML-панель не настроена.</p>
|
||||||
<p class="hint">Для отображения контента добавьте поле <code>panel</code> в <code>plugin.json</code>.</p>
|
<p class="hint">Для отображения контента добавьте поле <code>panel</code> в <code>plugin.json</code>.</p>
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="plugin-frame-container">
|
||||||
|
{#if error}
|
||||||
|
<div class="plugin-error-bar">⚠ {error}</div>
|
||||||
|
{/if}
|
||||||
|
<iframe
|
||||||
|
bind:this={iframeEl}
|
||||||
|
class="plugin-frame"
|
||||||
|
srcdoc={htmlPanel}
|
||||||
|
sandbox="allow-scripts allow-same-origin"
|
||||||
|
title="{pluginName} panel"
|
||||||
|
></iframe>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -232,5 +236,17 @@
|
||||||
.error { color: #f87171; }
|
.error { color: #f87171; }
|
||||||
.empty-state { text-align: center; padding: 3rem 1rem; color: var(--text-dim, #888); }
|
.empty-state { text-align: center; padding: 3rem 1rem; color: var(--text-dim, #888); }
|
||||||
.empty-state code { background: var(--surface-alt, #252538); padding: 0.15rem 0.4rem; border-radius: 3px; font-size: 0.85rem; }
|
.empty-state code { background: var(--surface-alt, #252538); padding: 0.15rem 0.4rem; border-radius: 3px; font-size: 0.85rem; }
|
||||||
|
.plugin-frame-container { flex: 1; display: flex; flex-direction: column; min-height: 0; }
|
||||||
|
.plugin-error-bar {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: rgba(248, 113, 113, 0.12);
|
||||||
|
border: 1px solid #f87171;
|
||||||
|
border-bottom: none;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
color: #f87171;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
.plugin-frame { flex: 1; border: 1px solid var(--border, #2a2a3e); border-radius: 8px; background: #fff; width: 100%; min-height: 400px; display: block; }
|
.plugin-frame { flex: 1; border: 1px solid var(--border, #2a2a3e); border-radius: 8px; background: #fff; width: 100%; min-height: 400px; display: block; }
|
||||||
|
.plugin-frame-container .plugin-frame { border-radius: 0 0 8px 8px; border-top: none; }
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -255,7 +255,15 @@ func (vm *LuaVM) CallFunctionJSON(segments []string, paramsJSON string) (string,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return ret.String(), nil
|
// Convert Lua return value to JSON for the frontend.
|
||||||
|
// Lua tables (from db.query, etc.) return "table: 0x..." via .String() —
|
||||||
|
// the frontend always expects valid JSON for JSON.parse().
|
||||||
|
goVal := luaValueToGo(ret)
|
||||||
|
jsonBytes, marshalErr := json.Marshal(goVal)
|
||||||
|
if marshalErr != nil {
|
||||||
|
return "", fmt.Errorf("failed to marshal return value: %w", marshalErr)
|
||||||
|
}
|
||||||
|
return string(jsonBytes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LState returns the underlying lua.LState (for table creation outside CallFunctionJSON).
|
// LState returns the underlying lua.LState (for table creation outside CallFunctionJSON).
|
||||||
|
|
|
||||||
|
|
@ -1569,8 +1569,8 @@ end
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CallFunctionJSON: %v", err)
|
t.Fatalf("CallFunctionJSON: %v", err)
|
||||||
}
|
}
|
||||||
if result != "hello:42" {
|
if result != `"hello:42"` {
|
||||||
t.Errorf("echo_json = %q, want %q", result, "hello:42")
|
t.Errorf("echo_json = %q, want %q", result, `"hello:42"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test with JSON array
|
// Test with JSON array
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue