pull/1120/head
Zef Hemel 2024-10-11 15:34:27 +02:00
parent 9f6063998f
commit 64e398fd90
28 changed files with 771 additions and 684 deletions

View File

@ -1,4 +1,3 @@
image: image:
file: .gitpod.Dockerfile file: .gitpod.Dockerfile

View File

@ -14,5 +14,6 @@
}, },
"[typescript]": { "[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno" "editor.defaultFormatter": "denoland.vscode-deno"
} },
"editor.tabSize": 2
} }

View File

@ -6,7 +6,6 @@ import {
LuaNativeJSFunction, LuaNativeJSFunction,
LuaRuntimeError, LuaRuntimeError,
} from "$common/space_lua/runtime.ts"; } from "$common/space_lua/runtime.ts";
import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts";
import { parse as parseLua } from "$common/space_lua/parse.ts"; import { parse as parseLua } from "$common/space_lua/parse.ts";
import { evalStatement } from "$common/space_lua/eval.ts"; import { evalStatement } from "$common/space_lua/eval.ts";
import { jsToLuaValue } from "$common/space_lua/runtime.ts"; import { jsToLuaValue } from "$common/space_lua/runtime.ts";
@ -19,6 +18,9 @@ import {
import type { ScriptEnvironment } from "$common/space_script.ts"; import type { ScriptEnvironment } from "$common/space_script.ts";
import { luaValueToJS } from "$common/space_lua/runtime.ts"; import { luaValueToJS } from "$common/space_lua/runtime.ts";
import type { ASTCtx } from "$common/space_lua/ast.ts"; import type { ASTCtx } from "$common/space_lua/ast.ts";
import { lua } from "@silverbulletmd/silverbullet/syscalls";
import type { ObjectQuery } from "@silverbulletmd/silverbullet/types";
import { buildLuaEnv } from "$common/space_lua_api.ts";
export class SpaceLuaEnvironment { export class SpaceLuaEnvironment {
env: LuaEnv = new LuaEnv(); env: LuaEnv = new LuaEnv();
@ -30,71 +32,17 @@ export class SpaceLuaEnvironment {
async reload(system: System<any>, scriptEnv: ScriptEnvironment) { async reload(system: System<any>, scriptEnv: ScriptEnvironment) {
const allScripts: ScriptObject[] = await system.invokeFunction( const allScripts: ScriptObject[] = await system.invokeFunction(
"index.queryObjects", "index.queryObjects",
["space-lua", {}], ["space-lua", {
// This is a bit silly, but at least makes the order deterministic
orderBy: [{ expr: ["attr", "ref"] }],
} as ObjectQuery],
); );
// We start from scratch this.env = buildLuaEnv(system, scriptEnv);
this.env = new LuaEnv(luaBuildStandardEnv());
const env = this.env;
// Expose all syscalls to Lua
for (const syscallName of system.registeredSyscalls.keys()) {
const [ns, fn] = syscallName.split(".");
if (!env.get(ns)) {
env.set(ns, new LuaTable());
}
env.get(ns).set(
fn,
new LuaNativeJSFunction((...args) => {
return system.localSyscall(syscallName, args);
}),
);
}
env.set(
"command",
new LuaBuiltinFunction(
(def: LuaTable) => {
if (def.get(1) === undefined) {
throw new Error("Callback is required");
}
console.log("Registering Lua command", def.get("name"));
scriptEnv.registerCommand(
def.toJSObject() as any,
async (...args: any[]) => {
try {
return await def.get(1).call(...args.map(jsToLuaValue));
} catch (e: any) {
console.error("Lua eval exception", e.message, e.context);
if (e.context && e.context.ref) {
// We got an error and actually know where it came from, let's navigate there to help debugging
const pageRef = parsePageRef(e.context.ref);
await system.localSyscall("editor.flashNotification", [
`Lua error: ${e.message}`,
"error",
]);
await system.localSyscall("editor.flashNotification", [
`Navigating to the place in the code where this error occurred in ${pageRef.page}`,
"info",
]);
await system.localSyscall("editor.navigate", [
{
page: pageRef.page,
pos: pageRef.pos + e.context.from +
"```space-lua\n".length,
},
]);
}
}
},
);
},
),
);
for (const script of allScripts) { for (const script of allScripts) {
try { try {
const ast = parseLua(script.script, { ref: script.ref }); const ast = parseLua(script.script, { ref: script.ref });
// We create a local scope for each script // We create a local scope for each script
const scriptEnv = new LuaEnv(env); const scriptEnv = new LuaEnv(this.env);
await evalStatement(ast, scriptEnv); await evalStatement(ast, scriptEnv);
} catch (e: any) { } catch (e: any) {
if (e instanceof LuaRuntimeError) { if (e instanceof LuaRuntimeError) {
@ -111,9 +59,10 @@ export class SpaceLuaEnvironment {
); );
} }
} }
// Find all functions and register them // Find all functions and register them
for (const globalName of env.keys()) { for (const globalName of this.env.keys()) {
const value = env.get(globalName); const value = this.env.get(globalName);
if (value instanceof LuaFunction) { if (value instanceof LuaFunction) {
console.log("Now registering Lua function", globalName); console.log("Now registering Lua function", globalName);
scriptEnv.registerFunction({ name: globalName }, (...args: any[]) => { scriptEnv.registerFunction({ name: globalName }, (...args: any[]) => {

View File

@ -295,7 +295,7 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
} }
} }
toJSObject(): Record<string, any> { asJSObject(): Record<string, any> {
const result: Record<string, any> = {}; const result: Record<string, any> = {};
for (const key of this.keys()) { for (const key of this.keys()) {
result[key] = luaValueToJS(this.get(key)); result[key] = luaValueToJS(this.get(key));
@ -303,6 +303,10 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
return result; return result;
} }
asJSArray(): any[] {
return this.arrayPart.map(luaValueToJS);
}
toString(): string { toString(): string {
if (this.metatable?.has("__tostring")) { if (this.metatable?.has("__tostring")) {
const metaValue = this.metatable.get("__tostring"); const metaValue = this.metatable.get("__tostring");

View File

@ -54,8 +54,7 @@ export const osApi = new LuaTable({
"%e": () => date.getDate().toString(), "%e": () => date.getDate().toString(),
// Hour // Hour
"%H": () => date.getHours().toString().padStart(2, "0"), "%H": () => date.getHours().toString().padStart(2, "0"),
"%I": () => "%I": () => (date.getHours() % 12 || 12).toString().padStart(2, "0"),
(date.getHours() % 12 || 12).toString().padStart(2, "0"),
// Minute // Minute
"%M": () => date.getMinutes().toString().padStart(2, "0"), "%M": () => date.getMinutes().toString().padStart(2, "0"),
// Second // Second

91
common/space_lua_api.ts Normal file
View File

@ -0,0 +1,91 @@
import { luaBuildStandardEnv } from "$common/space_lua/stdlib.ts";
import { parsePageRef } from "@silverbulletmd/silverbullet/lib/page_ref";
import {
jsToLuaValue,
LuaBuiltinFunction,
LuaEnv,
LuaNativeJSFunction,
LuaTable,
} from "$common/space_lua/runtime.ts";
import type { System } from "$lib/plugos/system.ts";
import type { ScriptEnvironment } from "$common/space_script.ts";
export function buildLuaEnv(system: System<any>, scriptEnv: ScriptEnvironment) {
const env = new LuaEnv(luaBuildStandardEnv());
// Expose all syscalls to Lua
for (const syscallName of system.registeredSyscalls.keys()) {
const [ns, fn] = syscallName.split(".");
if (!env.get(ns)) {
env.set(ns, new LuaTable());
}
const luaFn = new LuaNativeJSFunction((...args) => {
return system.localSyscall(syscallName, args);
});
// Register the function with the same name as the syscall both in regular and snake_case
env.get(ns).set(fn, luaFn);
env.get(ns).set(snakeCase(fn), luaFn);
}
// Expose the command registration function to Lua
env.set(
"command",
new LuaBuiltinFunction(
(def: LuaTable) => {
if (def.get(1) === undefined) {
throw new Error("Callback is required");
}
if (!def.get("name")) {
throw new Error("Name is required");
}
console.log("Registering Lua command", def.get("name"));
scriptEnv.registerCommand(
def.asJSObject() as any,
async (...args: any[]) => {
try {
return await def.get(1).call(
...args.map(jsToLuaValue),
);
} catch (e: any) {
console.error(
"Lua eval exception",
e.message,
e.context,
);
if (e.context && e.context.ref) {
// We got an error and actually know where it came from, let's navigate there to help debugging
const pageRef = parsePageRef(e.context.ref);
await system.localSyscall(
"editor.flashNotification",
[
`Lua error: ${e.message}`,
"error",
],
);
await system.localSyscall(
"editor.flashNotification",
[
`Navigating to the place in the code where this error occurred in ${pageRef.page}`,
"info",
],
);
await system.localSyscall("editor.navigate", [
{
page: pageRef.page,
pos: pageRef.pos + e.context.from +
"```space-lua\n".length,
},
]);
}
}
},
);
},
),
);
return env;
}
function snakeCase(s: string) {
return s.replace(/([A-Z])/g, "_$1").toLowerCase();
}

View File

@ -6,4 +6,3 @@ functions:
name: "Sync: Now" name: "Sync: Now"
key: "Alt-Shift-s" key: "Alt-Shift-s"
mac: "Cmd-Shift-s" mac: "Cmd-Shift-s"

View File

@ -1,15 +1,29 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1"
/>
<link rel="icon" type="image/x-icon" href="/favicon.png" /> <link rel="icon" type="image/x-icon" href="/favicon.png" />
<title>Login to SilverBullet</title> <title>Login to SilverBullet</title>
<style> <style>
html, html,
body { body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-family:
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
"Noto Sans",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
border: 0; border: 0;
margin: 0; margin: 0;
} }
@ -50,21 +64,36 @@
color: red; color: red;
} }
</style> </style>
</head> </head>
<body> <body>
<header> <header>
<h1>Login to <img src="/.client/logo.png" style="height: 1ch;" /> SilverBullet</h1> <h1>
Login to <img src="/.client/logo.png" style="height: 1ch" />
SilverBullet
</h1>
</header> </header>
<form action="/.auth" method="POST" id="login"> <form action="/.auth" method="POST" id="login">
<div class="error-message"></div> <div class="error-message"></div>
<div> <div>
<input type="text" name="username" id="username" autocomplete="off" autocorrect="off" autocapitalize="off" <input
autofocus placeholder="Username" /> type="text"
name="username"
id="username"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
autofocus
placeholder="Username"
/>
</div> </div>
<div> <div>
<input type="password" name="password" id="password" placeholder="Password" /> <input
type="password"
name="password"
id="password"
placeholder="Password"
/>
</div> </div>
<div> <div>
<input type="submit" value="Login" /> <input type="submit" value="Login" />
@ -77,11 +106,13 @@
<script> <script>
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const error = params.get('error'); const error = params.get("error");
if (error === "0") { if (error === "0") {
document.querySelector('.error-message').innerText = "The sent data was invalid"; document.querySelector(".error-message").innerText =
"The sent data was invalid";
} else if (error === "1") { } else if (error === "1") {
document.querySelector('.error-message').innerText = "Invalid username or password"; document.querySelector(".error-message").innerText =
"Invalid username or password";
} }
const from = params.get("from"); const from = params.get("from");
@ -95,5 +126,4 @@
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -147,7 +147,8 @@ export class Client implements ConfigContainer {
this.fullSyncCompleted = true; this.fullSyncCompleted = true;
} }
// Generate a semi-unique prefix for the database so not to reuse databases for different space paths // Generate a semi-unique prefix for the database so not to reuse databases for different space paths
this.dbPrefix = "" + simpleHash(globalThis.silverBulletConfig.spaceFolderPath); this.dbPrefix = "" +
simpleHash(globalThis.silverBulletConfig.spaceFolderPath);
this.onLoadPageRef = parsePageRefFromURI(); this.onLoadPageRef = parsePageRefFromURI();
} }

View File

@ -1,19 +1,28 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" prefix="og: https://ogp.me/ns#"> <html lang="en" prefix="og: https://ogp.me/ns#">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" <meta
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" /> name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<base href="/" /> <base href="/" />
<link rel="apple-touch-icon" href="/.client/logo.png"> <link rel="apple-touch-icon" href="/.client/logo.png" />
<meta name="theme-color" content="#e1e1e1" media="(prefers-color-scheme: light)"> <meta
<meta name="theme-color" content="#262626" media="(prefers-color-scheme: dark)"> name="theme-color"
content="#e1e1e1"
media="(prefers-color-scheme: light)"
/>
<meta
name="theme-color"
content="#262626"
media="(prefers-color-scheme: dark)"
/>
<meta name="referrer" content="no-referrer" /> <meta name="referrer" content="no-referrer" />
<title>{{TITLE}}</title> <title>{{TITLE}}</title>
<meta property="og:image" content="/.client/logo.png"> <meta property="og:image" content="/.client/logo.png" />
<meta property="og:description" content={{DESCRIPTION}}> <meta property="og:description" content="{{DESCRIPTION}}" />
<script> <script>
// Some global variables we need to make this work // Some global variables we need to make this work
@ -29,7 +38,7 @@
}, },
setHasTickScheduled() { setHasTickScheduled() {
// console.log("Not supported"); // console.log("Not supported");
} },
}, },
env: { env: {
get(key) { get(key) {
@ -38,7 +47,7 @@
}, },
errors: { errors: {
AlreadyExists: class extends Error {}, AlreadyExists: class extends Error {},
} },
}; };
window.silverBulletConfig = { window.silverBulletConfig = {
// These {{VARIABLES}} are replaced by http_server.ts // These {{VARIABLES}} are replaced by http_server.ts
@ -66,7 +75,6 @@
<body> <body>
<div id="sb-root"> <div id="sb-root">
<div id="sb-main"> <div id="sb-main">
<div id="sb-editor"> <div id="sb-editor">
<div class="cm-editor"> <div class="cm-editor">
@ -78,5 +86,4 @@
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View File

@ -361,7 +361,11 @@ tbody tr:nth-of-type(even) {
} }
.sb-admonition-title { .sb-admonition-title {
background-color: color-mix(in srgb, var(--admonition-color), transparent 90%) background-color: color-mix(
in srgb,
var(--admonition-color),
transparent 90%
);
} }
.sb-admonition-type::before { .sb-admonition-type::before {

View File

@ -360,7 +360,7 @@
} }
.sb-task-deadline { .sb-task-deadline {
background-color: rgba(22, 22, 22, 0.07) background-color: rgba(22, 22, 22, 0.07);
} }
.sb-line-frontmatter-outside, .sb-line-frontmatter-outside,
@ -420,7 +420,6 @@
white-space: nowrap; white-space: nowrap;
} }
// dont apply background color twice for (fenced) code blocks // dont apply background color twice for (fenced) code blocks
.sb-line-code .sb-code { .sb-line-code .sb-code {
background-color: transparent; background-color: transparent;
@ -592,7 +591,6 @@
button:last-of-type { button:last-of-type {
margin-right: 2px; margin-right: 2px;
} }
} }
} }

View File

@ -90,9 +90,7 @@ body {
border-radius: 5px; border-radius: 5px;
} }
} }
} }
} }
#sb-current-page { #sb-current-page {
@ -169,7 +167,6 @@ body {
border-radius: 5px; border-radius: 5px;
} }
#sb-main { #sb-main {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -50,7 +50,11 @@ html {
--button-color: black; --button-color: black;
--button-border-color: #6c6c6c; --button-border-color: #6c6c6c;
--primary-button-background-color: var(--ui-accent-color); --primary-button-background-color: var(--ui-accent-color);
--primary-button-hover-background-color: color-mix(in srgb, var(--ui-accent-color), black 35%); --primary-button-hover-background-color: color-mix(
in srgb,
var(--ui-accent-color),
black 35%
);
--primary-button-color: var(--ui-accent-contrast-color); --primary-button-color: var(--ui-accent-contrast-color);
--primary-button-border-color: transparent; --primary-button-border-color: transparent;
@ -120,10 +124,10 @@ html {
--editor-directive-color: #696969; --editor-directive-color: #696969;
--editor-directive-background-color: #ebebeb7d; --editor-directive-background-color: #ebebeb7d;
--ui-font:
--ui-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
"Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; "Segoe UI Symbol", "Noto Color Emoji";
--editor-font: "iA-Mono", "Menlo"; --editor-font: "iA-Mono", "Menlo";
--editor-width: 800px; --editor-width: 800px;
} }
@ -186,7 +190,11 @@ html[data-theme="dark"] {
--button-color: white; --button-color: white;
--button-border-color: #666; --button-border-color: #666;
--primary-button-background-color: var(--ui-accent-color); --primary-button-background-color: var(--ui-accent-color);
--primary-button-hover-background-color: color-mix(in srgb, var(--ui-accent-color), black 35%); --primary-button-hover-background-color: color-mix(
in srgb,
var(--ui-accent-color),
black 35%
);
--primary-button-color: var(--ui-accent-contrast-color); --primary-button-color: var(--ui-accent-contrast-color);
--primary-button-border-color: transparent; --primary-button-border-color: transparent;