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:
file: .gitpod.Dockerfile

View File

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

View File

@ -1,6 +1,6 @@
name: test
requiredPermissions:
- shell
- shell
functions:
run:
path: plug_test.ts:run

View File

@ -6,7 +6,6 @@ import {
LuaNativeJSFunction,
LuaRuntimeError,
} 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 { evalStatement } from "$common/space_lua/eval.ts";
import { jsToLuaValue } from "$common/space_lua/runtime.ts";
@ -19,6 +18,9 @@ import {
import type { ScriptEnvironment } from "$common/space_script.ts";
import { luaValueToJS } from "$common/space_lua/runtime.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 {
env: LuaEnv = new LuaEnv();
@ -30,71 +32,17 @@ export class SpaceLuaEnvironment {
async reload(system: System<any>, scriptEnv: ScriptEnvironment) {
const allScripts: ScriptObject[] = await system.invokeFunction(
"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 = 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,
},
]);
}
}
},
);
},
),
);
this.env = buildLuaEnv(system, scriptEnv);
for (const script of allScripts) {
try {
const ast = parseLua(script.script, { ref: script.ref });
// We create a local scope for each script
const scriptEnv = new LuaEnv(env);
const scriptEnv = new LuaEnv(this.env);
await evalStatement(ast, scriptEnv);
} catch (e: any) {
if (e instanceof LuaRuntimeError) {
@ -111,9 +59,10 @@ export class SpaceLuaEnvironment {
);
}
}
// Find all functions and register them
for (const globalName of env.keys()) {
const value = env.get(globalName);
for (const globalName of this.env.keys()) {
const value = this.env.get(globalName);
if (value instanceof LuaFunction) {
console.log("Now registering Lua function", globalName);
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> = {};
for (const key of this.keys()) {
result[key] = luaValueToJS(this.get(key));
@ -303,6 +303,10 @@ export class LuaTable implements ILuaSettable, ILuaGettable {
return result;
}
asJSArray(): any[] {
return this.arrayPart.map(luaValueToJS);
}
toString(): string {
if (this.metatable?.has("__tostring")) {
const metaValue = this.metatable.get("__tostring");

View File

@ -54,8 +54,7 @@ export const osApi = new LuaTable({
"%e": () => date.getDate().toString(),
// Hour
"%H": () => date.getHours().toString().padStart(2, "0"),
"%I": () =>
(date.getHours() % 12 || 12).toString().padStart(2, "0"),
"%I": () => (date.getHours() % 12 || 12).toString().padStart(2, "0"),
// Minute
"%M": () => date.getMinutes().toString().padStart(2, "0"),
// 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"
key: "Alt-Shift-s"
mac: "Cmd-Shift-s"

View File

@ -1,15 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<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" />
<title>Login to SilverBullet</title>
<style>
html,
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;
margin: 0;
}
@ -42,7 +56,7 @@
font-size: 18px;
}
form>div {
form > div {
margin-bottom: 5px;
}
@ -50,21 +64,36 @@
color: red;
}
</style>
</head>
</head>
<body>
<body>
<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>
<form action="/.auth" method="POST" id="login">
<div class="error-message"></div>
<div>
<input type="text" name="username" id="username" autocomplete="off" autocorrect="off" autocapitalize="off"
autofocus placeholder="Username" />
<input
type="text"
name="username"
id="username"
autocomplete="off"
autocorrect="off"
autocapitalize="off"
autofocus
placeholder="Username"
/>
</div>
<div>
<input type="password" name="password" id="password" placeholder="Password" />
<input
type="password"
name="password"
id="password"
placeholder="Password"
/>
</div>
<div>
<input type="submit" value="Login" />
@ -77,11 +106,13 @@
<script>
const params = new URLSearchParams(window.location.search);
const error = params.get('error');
const error = params.get("error");
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") {
document.querySelector('.error-message').innerText = "Invalid username or password";
document.querySelector(".error-message").innerText =
"Invalid username or password";
}
const from = params.get("from");
@ -94,6 +125,5 @@
document.getElementById("login").appendChild(input);
}
</script>
</body>
</body>
</html>

View File

@ -147,7 +147,8 @@ export class Client implements ConfigContainer {
this.fullSyncCompleted = true;
}
// 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();
}

View File

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

View File

@ -60,7 +60,7 @@
font-family: var(--editor-font);
}
.sb-notifications>div {
.sb-notifications > div {
border: var(--notifications-border-color) 1px solid;
}
@ -361,7 +361,11 @@ tbody tr:nth-of-type(even) {
}
.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 {
@ -424,7 +428,7 @@ a,
}
a.sb-wiki-link-page-missing,
.sb-wiki-link-page-missing>.sb-wiki-link-page {
.sb-wiki-link-page-missing > .sb-wiki-link-page {
color: var(--editor-wiki-link-page-missing-color);
background-color: var(--editor-wiki-link-page-background-color);
}

View File

@ -264,7 +264,7 @@
text-indent: -7ch;
}
.sb-checkbox>input[type="checkbox"] {
.sb-checkbox > input[type="checkbox"] {
width: 3ch;
}
@ -339,7 +339,7 @@
}
a.sb-wiki-link-page-missing,
.sb-wiki-link-page-missing>.sb-wiki-link-page {
.sb-wiki-link-page-missing > .sb-wiki-link-page {
border-radius: 5px;
padding: 0 5px;
// white-space: nowrap;
@ -360,7 +360,7 @@
}
.sb-task-deadline {
background-color: rgba(22, 22, 22, 0.07)
background-color: rgba(22, 22, 22, 0.07);
}
.sb-line-frontmatter-outside,
@ -420,7 +420,6 @@
white-space: nowrap;
}
// dont apply background color twice for (fenced) code blocks
.sb-line-code .sb-code {
background-color: transparent;
@ -439,7 +438,7 @@
margin: 0 3px;
}
.sb-code-copy-button>svg {
.sb-code-copy-button > svg {
height: 1rem;
width: 1rem;
}
@ -592,7 +591,6 @@
button:last-of-type {
margin-right: 2px;
}
}
}
@ -614,7 +612,7 @@
}
}
.sb-line-blockquote.sb-line-ul.sb-line-li>.sb-quote.sb-meta:first-child {
.sb-line-blockquote.sb-line-ul.sb-line-li > .sb-quote.sb-meta:first-child {
margin-left: -1ch;
}

View File

@ -84,15 +84,13 @@ body {
font-size: 15px;
z-index: 100;
>div {
> div {
padding: 3px;
margin-bottom: 3px;
border-radius: 5px;
}
}
}
}
#sb-current-page {
@ -169,7 +167,6 @@ body {
border-radius: 5px;
}
#sb-main {
display: flex;
flex-direction: row;

View File

@ -50,7 +50,11 @@ html {
--button-color: black;
--button-border-color: #6c6c6c;
--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-border-color: transparent;
@ -120,10 +124,10 @@ html {
--editor-directive-color: #696969;
--editor-directive-background-color: #ebebeb7d;
--ui-font: -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";
--ui-font:
-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";
--editor-font: "iA-Mono", "Menlo";
--editor-width: 800px;
}
@ -186,7 +190,11 @@ html[data-theme="dark"] {
--button-color: white;
--button-border-color: #666;
--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-border-color: transparent;