deno fmt
parent
9f6063998f
commit
64e398fd90
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
image:
|
image:
|
||||||
file: .gitpod.Dockerfile
|
file: .gitpod.Dockerfile
|
||||||
|
|
||||||
|
@ -20,7 +19,7 @@ tasks:
|
||||||
deno task install
|
deno task install
|
||||||
gp sync-done setup
|
gp sync-done setup
|
||||||
- name: Server watcher
|
- name: Server watcher
|
||||||
init: |
|
init: |
|
||||||
gp sync-await setup
|
gp sync-await setup
|
||||||
mkdir pages
|
mkdir pages
|
||||||
command: deno task watch-server pages
|
command: deno task watch-server pages
|
||||||
|
@ -33,4 +32,4 @@ tasks:
|
||||||
|
|
||||||
vscode:
|
vscode:
|
||||||
extensions:
|
extensions:
|
||||||
- denoland.vscode-deno
|
- denoland.vscode-deno
|
||||||
|
|
|
@ -14,5 +14,6 @@
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "denoland.vscode-deno"
|
"editor.defaultFormatter": "denoland.vscode-deno"
|
||||||
}
|
},
|
||||||
|
"editor.tabSize": 2
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: test
|
name: test
|
||||||
requiredPermissions:
|
requiredPermissions:
|
||||||
- shell
|
- shell
|
||||||
functions:
|
functions:
|
||||||
run:
|
run:
|
||||||
path: plug_test.ts:run
|
path: plug_test.ts:run
|
||||||
|
|
|
@ -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[]) => {
|
||||||
|
|
|
@ -5,38 +5,38 @@ import { evalStatement } from "$common/space_lua/eval.ts";
|
||||||
import { assert } from "@std/assert/assert";
|
import { assert } from "@std/assert/assert";
|
||||||
|
|
||||||
Deno.test("Lua language tests", async () => {
|
Deno.test("Lua language tests", async () => {
|
||||||
// Read the Lua file
|
// Read the Lua file
|
||||||
const luaFile = await Deno.readTextFile(
|
const luaFile = await Deno.readTextFile(
|
||||||
new URL("./language_test.lua", import.meta.url).pathname,
|
new URL("./language_test.lua", import.meta.url).pathname,
|
||||||
);
|
);
|
||||||
const chunk = parse(luaFile, {});
|
const chunk = parse(luaFile, {});
|
||||||
const env = new LuaEnv(luaBuildStandardEnv());
|
const env = new LuaEnv(luaBuildStandardEnv());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await evalStatement(chunk, env);
|
await evalStatement(chunk, env);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(`Error evaluating script:`, toPrettyString(e, luaFile));
|
console.error(`Error evaluating script:`, toPrettyString(e, luaFile));
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function toPrettyString(err: LuaRuntimeError, code: string): string {
|
function toPrettyString(err: LuaRuntimeError, code: string): string {
|
||||||
if (!err.context || !err.context.from || !err.context.to) {
|
if (!err.context || !err.context.from || !err.context.to) {
|
||||||
return err.toString();
|
return err.toString();
|
||||||
|
}
|
||||||
|
const from = err.context.from;
|
||||||
|
// Find the line and column
|
||||||
|
let line = 1;
|
||||||
|
let column = 0;
|
||||||
|
for (let i = 0; i < from; i++) {
|
||||||
|
if (code[i] === "\n") {
|
||||||
|
line++;
|
||||||
|
column = 0;
|
||||||
|
} else {
|
||||||
|
column++;
|
||||||
}
|
}
|
||||||
const from = err.context.from;
|
}
|
||||||
// Find the line and column
|
return `LuaRuntimeError: ${err.message} at ${line}:${column}:\n ${
|
||||||
let line = 1;
|
code.substring(from, err.context.to)
|
||||||
let column = 0;
|
}`;
|
||||||
for (let i = 0; i < from; i++) {
|
|
||||||
if (code[i] === "\n") {
|
|
||||||
line++;
|
|
||||||
column = 0;
|
|
||||||
} else {
|
|
||||||
column++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return `LuaRuntimeError: ${err.message} at ${line}:${column}:\n ${
|
|
||||||
code.substring(from, err.context.to)
|
|
||||||
}`;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +1,44 @@
|
||||||
import { assertEquals } from "@std/assert/equals";
|
import { assertEquals } from "@std/assert/equals";
|
||||||
import {
|
import {
|
||||||
jsToLuaValue,
|
jsToLuaValue,
|
||||||
luaLen,
|
luaLen,
|
||||||
LuaMultiRes,
|
LuaMultiRes,
|
||||||
} from "$common/space_lua/runtime.ts";
|
} from "$common/space_lua/runtime.ts";
|
||||||
|
|
||||||
Deno.test("Test Lua Rutime", () => {
|
Deno.test("Test Lua Rutime", () => {
|
||||||
// Test LuaMultires
|
// Test LuaMultires
|
||||||
assertEquals(new LuaMultiRes([]).flatten().values, []);
|
assertEquals(new LuaMultiRes([]).flatten().values, []);
|
||||||
assertEquals(new LuaMultiRes([1, 2, 3]).flatten().values, [1, 2, 3]);
|
assertEquals(new LuaMultiRes([1, 2, 3]).flatten().values, [1, 2, 3]);
|
||||||
assertEquals(
|
assertEquals(
|
||||||
new LuaMultiRes([1, new LuaMultiRes([2, 3])]).flatten().values,
|
new LuaMultiRes([1, new LuaMultiRes([2, 3])]).flatten().values,
|
||||||
[
|
[
|
||||||
1,
|
1,
|
||||||
2,
|
2,
|
||||||
3,
|
3,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test JavaScript to Lua conversion
|
// Test JavaScript to Lua conversion
|
||||||
assertEquals(jsToLuaValue(1), 1);
|
assertEquals(jsToLuaValue(1), 1);
|
||||||
assertEquals(jsToLuaValue("hello"), "hello");
|
assertEquals(jsToLuaValue("hello"), "hello");
|
||||||
// Arrays
|
// Arrays
|
||||||
let luaVal = jsToLuaValue([1, 2, 3]);
|
let luaVal = jsToLuaValue([1, 2, 3]);
|
||||||
assertEquals(luaLen(luaVal), 3);
|
assertEquals(luaLen(luaVal), 3);
|
||||||
assertEquals(luaVal.get(1), 1);
|
assertEquals(luaVal.get(1), 1);
|
||||||
// Objects
|
// Objects
|
||||||
luaVal = jsToLuaValue({ name: "Pete", age: 10 });
|
luaVal = jsToLuaValue({ name: "Pete", age: 10 });
|
||||||
assertEquals(luaVal.get("name"), "Pete");
|
assertEquals(luaVal.get("name"), "Pete");
|
||||||
assertEquals(luaVal.get("age"), 10);
|
assertEquals(luaVal.get("age"), 10);
|
||||||
// Nested objects
|
// Nested objects
|
||||||
luaVal = jsToLuaValue({ name: "Pete", list: [1, 2, 3] });
|
luaVal = jsToLuaValue({ name: "Pete", list: [1, 2, 3] });
|
||||||
assertEquals(luaVal.get("name"), "Pete");
|
assertEquals(luaVal.get("name"), "Pete");
|
||||||
assertEquals(luaLen(luaVal.get("list")), 3);
|
assertEquals(luaLen(luaVal.get("list")), 3);
|
||||||
assertEquals(luaVal.get("list").get(2), 2);
|
assertEquals(luaVal.get("list").get(2), 2);
|
||||||
luaVal = jsToLuaValue([{ name: "Pete" }, { name: "John" }]);
|
luaVal = jsToLuaValue([{ name: "Pete" }, { name: "John" }]);
|
||||||
assertEquals(luaLen(luaVal), 2);
|
assertEquals(luaLen(luaVal), 2);
|
||||||
assertEquals(luaVal.get(1).get("name"), "Pete");
|
assertEquals(luaVal.get(1).get("name"), "Pete");
|
||||||
assertEquals(luaVal.get(2).get("name"), "John");
|
assertEquals(luaVal.get(2).get("name"), "John");
|
||||||
// Functions in objects
|
// Functions in objects
|
||||||
luaVal = jsToLuaValue({ name: "Pete", first: (l: any[]) => l[0] });
|
luaVal = jsToLuaValue({ name: "Pete", first: (l: any[]) => l[0] });
|
||||||
assertEquals(luaVal.get("first").call([1, 2, 3]), 1);
|
assertEquals(luaVal.get("first").call([1, 2, 3]), 1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import {
|
import {
|
||||||
type ILuaFunction,
|
type ILuaFunction,
|
||||||
LuaBuiltinFunction,
|
LuaBuiltinFunction,
|
||||||
LuaEnv,
|
LuaEnv,
|
||||||
LuaMultiRes,
|
LuaMultiRes,
|
||||||
type LuaTable,
|
type LuaTable,
|
||||||
luaToString,
|
luaToString,
|
||||||
luaTypeOf,
|
luaTypeOf,
|
||||||
type LuaValue,
|
type LuaValue,
|
||||||
} from "$common/space_lua/runtime.ts";
|
} from "$common/space_lua/runtime.ts";
|
||||||
import { stringApi } from "$common/space_lua/stdlib/string.ts";
|
import { stringApi } from "$common/space_lua/stdlib/string.ts";
|
||||||
import { tableApi } from "$common/space_lua/stdlib/table.ts";
|
import { tableApi } from "$common/space_lua/stdlib/table.ts";
|
||||||
|
@ -14,129 +14,129 @@ import { osApi } from "$common/space_lua/stdlib/os.ts";
|
||||||
import { jsApi } from "$common/space_lua/stdlib/js.ts";
|
import { jsApi } from "$common/space_lua/stdlib/js.ts";
|
||||||
|
|
||||||
const printFunction = new LuaBuiltinFunction((...args) => {
|
const printFunction = new LuaBuiltinFunction((...args) => {
|
||||||
console.log("[Lua]", ...args.map(luaToString));
|
console.log("[Lua]", ...args.map(luaToString));
|
||||||
});
|
});
|
||||||
|
|
||||||
const assertFunction = new LuaBuiltinFunction(
|
const assertFunction = new LuaBuiltinFunction(
|
||||||
async (value: any, message?: string) => {
|
async (value: any, message?: string) => {
|
||||||
if (!await value) {
|
if (!await value) {
|
||||||
throw new Error(`Assertion failed: ${message}`);
|
throw new Error(`Assertion failed: ${message}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const ipairsFunction = new LuaBuiltinFunction((ar: LuaTable) => {
|
const ipairsFunction = new LuaBuiltinFunction((ar: LuaTable) => {
|
||||||
let i = 1;
|
let i = 1;
|
||||||
return () => {
|
return () => {
|
||||||
if (i > ar.length) {
|
if (i > ar.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = new LuaMultiRes([i, ar.get(i)]);
|
const result = new LuaMultiRes([i, ar.get(i)]);
|
||||||
i++;
|
i++;
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const pairsFunction = new LuaBuiltinFunction((t: LuaTable) => {
|
const pairsFunction = new LuaBuiltinFunction((t: LuaTable) => {
|
||||||
const keys = t.keys();
|
const keys = t.keys();
|
||||||
let i = 0;
|
let i = 0;
|
||||||
return () => {
|
return () => {
|
||||||
if (i >= keys.length) {
|
if (i >= keys.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const key = keys[i];
|
const key = keys[i];
|
||||||
i++;
|
i++;
|
||||||
return new LuaMultiRes([key, t.get(key)]);
|
return new LuaMultiRes([key, t.get(key)]);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const unpackFunction = new LuaBuiltinFunction((t: LuaTable) => {
|
const unpackFunction = new LuaBuiltinFunction((t: LuaTable) => {
|
||||||
const values: LuaValue[] = [];
|
const values: LuaValue[] = [];
|
||||||
for (let i = 1; i <= t.length; i++) {
|
for (let i = 1; i <= t.length; i++) {
|
||||||
values.push(t.get(i));
|
values.push(t.get(i));
|
||||||
}
|
}
|
||||||
return new LuaMultiRes(values);
|
return new LuaMultiRes(values);
|
||||||
});
|
});
|
||||||
|
|
||||||
const typeFunction = new LuaBuiltinFunction((value: LuaValue): string => {
|
const typeFunction = new LuaBuiltinFunction((value: LuaValue): string => {
|
||||||
return luaTypeOf(value);
|
return luaTypeOf(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const tostringFunction = new LuaBuiltinFunction((value: any) => {
|
const tostringFunction = new LuaBuiltinFunction((value: any) => {
|
||||||
return luaToString(value);
|
return luaToString(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const tonumberFunction = new LuaBuiltinFunction((value: LuaValue) => {
|
const tonumberFunction = new LuaBuiltinFunction((value: LuaValue) => {
|
||||||
return Number(value);
|
return Number(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
const errorFunction = new LuaBuiltinFunction((message: string) => {
|
const errorFunction = new LuaBuiltinFunction((message: string) => {
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
});
|
});
|
||||||
|
|
||||||
const pcallFunction = new LuaBuiltinFunction(
|
const pcallFunction = new LuaBuiltinFunction(
|
||||||
async (fn: ILuaFunction, ...args) => {
|
async (fn: ILuaFunction, ...args) => {
|
||||||
try {
|
try {
|
||||||
return new LuaMultiRes([true, await fn.call(...args)]);
|
return new LuaMultiRes([true, await fn.call(...args)]);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return new LuaMultiRes([false, e.message]);
|
return new LuaMultiRes([false, e.message]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const xpcallFunction = new LuaBuiltinFunction(
|
const xpcallFunction = new LuaBuiltinFunction(
|
||||||
async (fn: ILuaFunction, errorHandler: ILuaFunction, ...args) => {
|
async (fn: ILuaFunction, errorHandler: ILuaFunction, ...args) => {
|
||||||
try {
|
try {
|
||||||
return new LuaMultiRes([true, await fn.call(...args)]);
|
return new LuaMultiRes([true, await fn.call(...args)]);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return new LuaMultiRes([false, await errorHandler.call(e.message)]);
|
return new LuaMultiRes([false, await errorHandler.call(e.message)]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const setmetatableFunction = new LuaBuiltinFunction(
|
const setmetatableFunction = new LuaBuiltinFunction(
|
||||||
(table: LuaTable, metatable: LuaTable) => {
|
(table: LuaTable, metatable: LuaTable) => {
|
||||||
table.metatable = metatable;
|
table.metatable = metatable;
|
||||||
return table;
|
return table;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const rawsetFunction = new LuaBuiltinFunction(
|
const rawsetFunction = new LuaBuiltinFunction(
|
||||||
(table: LuaTable, key: LuaValue, value: LuaValue) => {
|
(table: LuaTable, key: LuaValue, value: LuaValue) => {
|
||||||
table.rawSet(key, value);
|
table.rawSet(key, value);
|
||||||
return table;
|
return table;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const getmetatableFunction = new LuaBuiltinFunction((table: LuaTable) => {
|
const getmetatableFunction = new LuaBuiltinFunction((table: LuaTable) => {
|
||||||
return table.metatable;
|
return table.metatable;
|
||||||
});
|
});
|
||||||
|
|
||||||
export function luaBuildStandardEnv() {
|
export function luaBuildStandardEnv() {
|
||||||
const env = new LuaEnv();
|
const env = new LuaEnv();
|
||||||
// Top-level builtins
|
// Top-level builtins
|
||||||
env.set("print", printFunction);
|
env.set("print", printFunction);
|
||||||
env.set("assert", assertFunction);
|
env.set("assert", assertFunction);
|
||||||
env.set("type", typeFunction);
|
env.set("type", typeFunction);
|
||||||
env.set("tostring", tostringFunction);
|
env.set("tostring", tostringFunction);
|
||||||
env.set("tonumber", tonumberFunction);
|
env.set("tonumber", tonumberFunction);
|
||||||
env.set("unpack", unpackFunction);
|
env.set("unpack", unpackFunction);
|
||||||
// Iterators
|
// Iterators
|
||||||
env.set("pairs", pairsFunction);
|
env.set("pairs", pairsFunction);
|
||||||
env.set("ipairs", ipairsFunction);
|
env.set("ipairs", ipairsFunction);
|
||||||
// meta table stuff
|
// meta table stuff
|
||||||
env.set("setmetatable", setmetatableFunction);
|
env.set("setmetatable", setmetatableFunction);
|
||||||
env.set("getmetatable", getmetatableFunction);
|
env.set("getmetatable", getmetatableFunction);
|
||||||
env.set("rawset", rawsetFunction);
|
env.set("rawset", rawsetFunction);
|
||||||
// Error handling
|
// Error handling
|
||||||
env.set("error", errorFunction);
|
env.set("error", errorFunction);
|
||||||
env.set("pcall", pcallFunction);
|
env.set("pcall", pcallFunction);
|
||||||
env.set("xpcall", xpcallFunction);
|
env.set("xpcall", xpcallFunction);
|
||||||
|
|
||||||
// APIs
|
// APIs
|
||||||
env.set("string", stringApi);
|
env.set("string", stringApi);
|
||||||
env.set("table", tableApi);
|
env.set("table", tableApi);
|
||||||
env.set("os", osApi);
|
env.set("os", osApi);
|
||||||
env.set("js", jsApi);
|
env.set("js", jsApi);
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
import {
|
import {
|
||||||
jsToLuaValue,
|
jsToLuaValue,
|
||||||
LuaBuiltinFunction,
|
LuaBuiltinFunction,
|
||||||
LuaTable,
|
LuaTable,
|
||||||
luaValueToJS,
|
luaValueToJS,
|
||||||
} from "$common/space_lua/runtime.ts";
|
} from "$common/space_lua/runtime.ts";
|
||||||
|
|
||||||
export const jsApi = new LuaTable({
|
export const jsApi = new LuaTable({
|
||||||
new: new LuaBuiltinFunction(
|
new: new LuaBuiltinFunction(
|
||||||
(constructorFn: any, ...args) => {
|
(constructorFn: any, ...args) => {
|
||||||
return new constructorFn(
|
return new constructorFn(
|
||||||
...args.map(luaValueToJS),
|
...args.map(luaValueToJS),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
importModule: new LuaBuiltinFunction((url) => {
|
importModule: new LuaBuiltinFunction((url) => {
|
||||||
return import(url);
|
return import(url);
|
||||||
}),
|
}),
|
||||||
/**
|
/**
|
||||||
* Binds a function to an object, so that the function can be called with the object as `this`. Some JS APIs require this.
|
* Binds a function to an object, so that the function can be called with the object as `this`. Some JS APIs require this.
|
||||||
*/
|
*/
|
||||||
bind: new LuaBuiltinFunction((fn: any, obj: any, ...args: any[]) => {
|
bind: new LuaBuiltinFunction((fn: any, obj: any, ...args: any[]) => {
|
||||||
return fn.bind(obj, ...args);
|
return fn.bind(obj, ...args);
|
||||||
}),
|
}),
|
||||||
tolua: new LuaBuiltinFunction(jsToLuaValue),
|
tolua: new LuaBuiltinFunction(jsToLuaValue),
|
||||||
tojs: new LuaBuiltinFunction(luaValueToJS),
|
tojs: new LuaBuiltinFunction(luaValueToJS),
|
||||||
log: new LuaBuiltinFunction((...args) => {
|
log: new LuaBuiltinFunction((...args) => {
|
||||||
console.log(...args);
|
console.log(...args);
|
||||||
}),
|
}),
|
||||||
// assignGlobal: new LuaBuiltinFunction((name: string, value: any) => {
|
// assignGlobal: new LuaBuiltinFunction((name: string, value: any) => {
|
||||||
// (globalThis as any)[name] = value;
|
// (globalThis as any)[name] = value;
|
||||||
// }),
|
// }),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,103 +1,102 @@
|
||||||
import { LuaBuiltinFunction, LuaTable } from "$common/space_lua/runtime.ts";
|
import { LuaBuiltinFunction, LuaTable } from "$common/space_lua/runtime.ts";
|
||||||
|
|
||||||
export const osApi = new LuaTable({
|
export const osApi = new LuaTable({
|
||||||
time: new LuaBuiltinFunction((tbl?: LuaTable) => {
|
time: new LuaBuiltinFunction((tbl?: LuaTable) => {
|
||||||
if (tbl) {
|
if (tbl) {
|
||||||
// Build a date object from the table
|
// Build a date object from the table
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
if (!tbl.has("year")) {
|
if (!tbl.has("year")) {
|
||||||
throw new Error("time(): year is required");
|
throw new Error("time(): year is required");
|
||||||
}
|
}
|
||||||
date.setFullYear(tbl.get("year"));
|
date.setFullYear(tbl.get("year"));
|
||||||
if (!tbl.has("month")) {
|
if (!tbl.has("month")) {
|
||||||
throw new Error("time(): month is required");
|
throw new Error("time(): month is required");
|
||||||
}
|
}
|
||||||
date.setMonth(tbl.get("month") - 1);
|
date.setMonth(tbl.get("month") - 1);
|
||||||
if (!tbl.has("day")) {
|
if (!tbl.has("day")) {
|
||||||
throw new Error("time(): day is required");
|
throw new Error("time(): day is required");
|
||||||
}
|
}
|
||||||
date.setDate(tbl.get("day"));
|
date.setDate(tbl.get("day"));
|
||||||
date.setHours(tbl.get("hour") ?? 12);
|
date.setHours(tbl.get("hour") ?? 12);
|
||||||
date.setMinutes(tbl.get("min") ?? 0);
|
date.setMinutes(tbl.get("min") ?? 0);
|
||||||
date.setSeconds(tbl.get("sec") ?? 0);
|
date.setSeconds(tbl.get("sec") ?? 0);
|
||||||
return Math.floor(date.getTime() / 1000);
|
return Math.floor(date.getTime() / 1000);
|
||||||
} else {
|
} else {
|
||||||
return Math.floor(Date.now() / 1000);
|
return Math.floor(Date.now() / 1000);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
/**
|
/**
|
||||||
* Returns a string or a table containing date and time, formatted according to the given string format.
|
* Returns a string or a table containing date and time, formatted according to the given string format.
|
||||||
* If the time argument is present, this is the time to be formatted (see the os.time function for a description of this value). Otherwise, date formats the current time.
|
* If the time argument is present, this is the time to be formatted (see the os.time function for a description of this value). Otherwise, date formats the current time.
|
||||||
* If format starts with '!', then the date is formatted in Coordinated Universal Time. After this optional character, if format is the string "*t", then date returns a table with the following fields: year, month (1–12), day (1–31), hour (0–23), min (0–59), sec (0–61, due to leap seconds), wday (weekday, 1–7, Sunday is 1), yday (day of the year, 1–366), and isdst (daylight saving flag, a boolean). This last field may be absent if the information is not available.
|
* If format starts with '!', then the date is formatted in Coordinated Universal Time. After this optional character, if format is the string "*t", then date returns a table with the following fields: year, month (1–12), day (1–31), hour (0–23), min (0–59), sec (0–61, due to leap seconds), wday (weekday, 1–7, Sunday is 1), yday (day of the year, 1–366), and isdst (daylight saving flag, a boolean). This last field may be absent if the information is not available.
|
||||||
* If format is not "*t", then date returns the date as a string, formatted according to the same rules as the ISO C function strftime.
|
* If format is not "*t", then date returns the date as a string, formatted according to the same rules as the ISO C function strftime.
|
||||||
* If format is absent, it defaults to "%c", which gives a human-readable date and time representation using the current locale.
|
* If format is absent, it defaults to "%c", which gives a human-readable date and time representation using the current locale.
|
||||||
*/
|
*/
|
||||||
date: new LuaBuiltinFunction((format: string, timestamp?: number) => {
|
date: new LuaBuiltinFunction((format: string, timestamp?: number) => {
|
||||||
const date = timestamp ? new Date(timestamp * 1000) : new Date();
|
const date = timestamp ? new Date(timestamp * 1000) : new Date();
|
||||||
|
|
||||||
// Default Lua-like format when no format string is provided
|
// Default Lua-like format when no format string is provided
|
||||||
if (!format) {
|
if (!format) {
|
||||||
return date.toDateString() + " " + date.toLocaleTimeString();
|
return date.toDateString() + " " + date.toLocaleTimeString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define mappings for Lua-style placeholders
|
// Define mappings for Lua-style placeholders
|
||||||
const formatMap: { [key: string]: () => string } = {
|
const formatMap: { [key: string]: () => string } = {
|
||||||
// Year
|
// Year
|
||||||
"%Y": () => date.getFullYear().toString(),
|
"%Y": () => date.getFullYear().toString(),
|
||||||
"%y": () => (date.getFullYear() % 100).toString().padStart(2, "0"),
|
"%y": () => (date.getFullYear() % 100).toString().padStart(2, "0"),
|
||||||
// Month
|
// Month
|
||||||
"%m": () => (date.getMonth() + 1).toString().padStart(2, "0"),
|
"%m": () => (date.getMonth() + 1).toString().padStart(2, "0"),
|
||||||
"%b": () => date.toLocaleString("en-US", { month: "short" }),
|
"%b": () => date.toLocaleString("en-US", { month: "short" }),
|
||||||
"%B": () => date.toLocaleString("en-US", { month: "long" }),
|
"%B": () => date.toLocaleString("en-US", { month: "long" }),
|
||||||
// Day
|
// Day
|
||||||
"%d": () => date.getDate().toString().padStart(2, "0"),
|
"%d": () => date.getDate().toString().padStart(2, "0"),
|
||||||
"%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
|
"%S": () => date.getSeconds().toString().padStart(2, "0"),
|
||||||
"%S": () => date.getSeconds().toString().padStart(2, "0"),
|
// AM/PM
|
||||||
// AM/PM
|
"%p": () => date.getHours() >= 12 ? "PM" : "AM",
|
||||||
"%p": () => date.getHours() >= 12 ? "PM" : "AM",
|
// Day of the week
|
||||||
// Day of the week
|
"%A": () => date.toLocaleString("en-US", { weekday: "long" }),
|
||||||
"%A": () => date.toLocaleString("en-US", { weekday: "long" }),
|
"%a": () => date.toLocaleString("en-US", { weekday: "short" }),
|
||||||
"%a": () => date.toLocaleString("en-US", { weekday: "short" }),
|
"%w": () => date.getDay().toString(),
|
||||||
"%w": () => date.getDay().toString(),
|
// Day of the year
|
||||||
// Day of the year
|
"%j": () => {
|
||||||
"%j": () => {
|
const start = new Date(date.getFullYear(), 0, 0);
|
||||||
const start = new Date(date.getFullYear(), 0, 0);
|
const diff = date.getTime() - start.getTime();
|
||||||
const diff = date.getTime() - start.getTime();
|
const oneDay = 1000 * 60 * 60 * 24;
|
||||||
const oneDay = 1000 * 60 * 60 * 24;
|
const dayOfYear = Math.floor(diff / oneDay);
|
||||||
const dayOfYear = Math.floor(diff / oneDay);
|
return dayOfYear.toString().padStart(3, "0");
|
||||||
return dayOfYear.toString().padStart(3, "0");
|
},
|
||||||
},
|
// Time zone
|
||||||
// Time zone
|
"%Z": () => {
|
||||||
"%Z": () => {
|
const match = date.toTimeString().match(/\((.*)\)/);
|
||||||
const match = date.toTimeString().match(/\((.*)\)/);
|
return match ? match[1] : "";
|
||||||
return match ? match[1] : "";
|
},
|
||||||
},
|
"%z": () => {
|
||||||
"%z": () => {
|
const offset = -date.getTimezoneOffset();
|
||||||
const offset = -date.getTimezoneOffset();
|
const sign = offset >= 0 ? "+" : "-";
|
||||||
const sign = offset >= 0 ? "+" : "-";
|
const absOffset = Math.abs(offset);
|
||||||
const absOffset = Math.abs(offset);
|
const hours = Math.floor(absOffset / 60).toString().padStart(
|
||||||
const hours = Math.floor(absOffset / 60).toString().padStart(
|
2,
|
||||||
2,
|
"0",
|
||||||
"0",
|
);
|
||||||
);
|
const minutes = (absOffset % 60).toString().padStart(2, "0");
|
||||||
const minutes = (absOffset % 60).toString().padStart(2, "0");
|
return `${sign}${hours}${minutes}`;
|
||||||
return `${sign}${hours}${minutes}`;
|
},
|
||||||
},
|
// Literal %
|
||||||
// Literal %
|
"%%": () => "%",
|
||||||
"%%": () => "%",
|
};
|
||||||
};
|
|
||||||
|
|
||||||
// Replace format placeholders with corresponding values
|
// Replace format placeholders with corresponding values
|
||||||
return format.replace(/%[A-Za-z%]/g, (match) => {
|
return format.replace(/%[A-Za-z%]/g, (match) => {
|
||||||
const formatter = formatMap[match];
|
const formatter = formatMap[match];
|
||||||
return formatter ? formatter() : match;
|
return formatter ? formatter() : match;
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,106 +1,106 @@
|
||||||
import {
|
import {
|
||||||
LuaBuiltinFunction,
|
LuaBuiltinFunction,
|
||||||
LuaMultiRes,
|
LuaMultiRes,
|
||||||
LuaTable,
|
LuaTable,
|
||||||
luaToString,
|
luaToString,
|
||||||
} from "$common/space_lua/runtime.ts";
|
} from "$common/space_lua/runtime.ts";
|
||||||
|
|
||||||
export const stringApi = new LuaTable({
|
export const stringApi = new LuaTable({
|
||||||
byte: new LuaBuiltinFunction((s: string, i?: number, j?: number) => {
|
byte: new LuaBuiltinFunction((s: string, i?: number, j?: number) => {
|
||||||
i = i ?? 1;
|
i = i ?? 1;
|
||||||
j = j ?? i;
|
j = j ?? i;
|
||||||
const result = [];
|
const result = [];
|
||||||
for (let k = i; k <= j; k++) {
|
for (let k = i; k <= j; k++) {
|
||||||
result.push(s.charCodeAt(k - 1));
|
result.push(s.charCodeAt(k - 1));
|
||||||
|
}
|
||||||
|
return new LuaMultiRes(result);
|
||||||
|
}),
|
||||||
|
char: new LuaBuiltinFunction((...args: number[]) => {
|
||||||
|
return String.fromCharCode(...args);
|
||||||
|
}),
|
||||||
|
find: new LuaBuiltinFunction(
|
||||||
|
(s: string, pattern: string, init?: number, plain?: boolean) => {
|
||||||
|
init = init ?? 1;
|
||||||
|
plain = plain ?? false;
|
||||||
|
const result = s.slice(init - 1).match(pattern);
|
||||||
|
if (!result) {
|
||||||
|
return new LuaMultiRes([]);
|
||||||
|
}
|
||||||
|
return new LuaMultiRes([
|
||||||
|
result.index! + 1,
|
||||||
|
result.index! + result[0].length,
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
format: new LuaBuiltinFunction((format: string, ...args: any[]) => {
|
||||||
|
return format.replace(/%./g, (match) => {
|
||||||
|
switch (match) {
|
||||||
|
case "%s":
|
||||||
|
return luaToString(args.shift());
|
||||||
|
case "%d":
|
||||||
|
return String(args.shift());
|
||||||
|
default:
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
gmatch: new LuaBuiltinFunction((s: string, pattern: string) => {
|
||||||
|
const regex = new RegExp(pattern, "g");
|
||||||
|
return () => {
|
||||||
|
const result = regex.exec(s);
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return new LuaMultiRes(result.slice(1));
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
gsub: new LuaBuiltinFunction(
|
||||||
|
(s: string, pattern: string, repl: string, n?: number) => {
|
||||||
|
n = n ?? Infinity;
|
||||||
|
const regex = new RegExp(pattern, "g");
|
||||||
|
let result = s;
|
||||||
|
let match: RegExpExecArray | null;
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
match = regex.exec(result);
|
||||||
|
if (!match) {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return new LuaMultiRes(result);
|
result = result.replace(match[0], repl);
|
||||||
}),
|
}
|
||||||
char: new LuaBuiltinFunction((...args: number[]) => {
|
return result;
|
||||||
return String.fromCharCode(...args);
|
},
|
||||||
}),
|
),
|
||||||
find: new LuaBuiltinFunction(
|
len: new LuaBuiltinFunction((s: string) => {
|
||||||
(s: string, pattern: string, init?: number, plain?: boolean) => {
|
return s.length;
|
||||||
init = init ?? 1;
|
}),
|
||||||
plain = plain ?? false;
|
lower: new LuaBuiltinFunction((s: string) => {
|
||||||
const result = s.slice(init - 1).match(pattern);
|
return luaToString(s.toLowerCase());
|
||||||
if (!result) {
|
}),
|
||||||
return new LuaMultiRes([]);
|
upper: new LuaBuiltinFunction((s: string) => {
|
||||||
}
|
return luaToString(s.toUpperCase());
|
||||||
return new LuaMultiRes([
|
}),
|
||||||
result.index! + 1,
|
match: new LuaBuiltinFunction(
|
||||||
result.index! + result[0].length,
|
(s: string, pattern: string, init?: number) => {
|
||||||
]);
|
init = init ?? 1;
|
||||||
},
|
const result = s.slice(init - 1).match(pattern);
|
||||||
),
|
if (!result) {
|
||||||
format: new LuaBuiltinFunction((format: string, ...args: any[]) => {
|
return new LuaMultiRes([]);
|
||||||
return format.replace(/%./g, (match) => {
|
}
|
||||||
switch (match) {
|
return new LuaMultiRes(result.slice(1));
|
||||||
case "%s":
|
},
|
||||||
return luaToString(args.shift());
|
),
|
||||||
case "%d":
|
rep: new LuaBuiltinFunction((s: string, n: number, sep?: string) => {
|
||||||
return String(args.shift());
|
sep = sep ?? "";
|
||||||
default:
|
return s.repeat(n) + sep;
|
||||||
return match;
|
}),
|
||||||
}
|
reverse: new LuaBuiltinFunction((s: string) => {
|
||||||
});
|
return s.split("").reverse().join("");
|
||||||
}),
|
}),
|
||||||
gmatch: new LuaBuiltinFunction((s: string, pattern: string) => {
|
sub: new LuaBuiltinFunction((s: string, i: number, j?: number) => {
|
||||||
const regex = new RegExp(pattern, "g");
|
j = j ?? s.length;
|
||||||
return () => {
|
return s.slice(i - 1, j);
|
||||||
const result = regex.exec(s);
|
}),
|
||||||
if (!result) {
|
split: new LuaBuiltinFunction((s: string, sep: string) => {
|
||||||
return;
|
return s.split(sep);
|
||||||
}
|
}),
|
||||||
return new LuaMultiRes(result.slice(1));
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
gsub: new LuaBuiltinFunction(
|
|
||||||
(s: string, pattern: string, repl: string, n?: number) => {
|
|
||||||
n = n ?? Infinity;
|
|
||||||
const regex = new RegExp(pattern, "g");
|
|
||||||
let result = s;
|
|
||||||
let match: RegExpExecArray | null;
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
match = regex.exec(result);
|
|
||||||
if (!match) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
result = result.replace(match[0], repl);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
len: new LuaBuiltinFunction((s: string) => {
|
|
||||||
return s.length;
|
|
||||||
}),
|
|
||||||
lower: new LuaBuiltinFunction((s: string) => {
|
|
||||||
return luaToString(s.toLowerCase());
|
|
||||||
}),
|
|
||||||
upper: new LuaBuiltinFunction((s: string) => {
|
|
||||||
return luaToString(s.toUpperCase());
|
|
||||||
}),
|
|
||||||
match: new LuaBuiltinFunction(
|
|
||||||
(s: string, pattern: string, init?: number) => {
|
|
||||||
init = init ?? 1;
|
|
||||||
const result = s.slice(init - 1).match(pattern);
|
|
||||||
if (!result) {
|
|
||||||
return new LuaMultiRes([]);
|
|
||||||
}
|
|
||||||
return new LuaMultiRes(result.slice(1));
|
|
||||||
},
|
|
||||||
),
|
|
||||||
rep: new LuaBuiltinFunction((s: string, n: number, sep?: string) => {
|
|
||||||
sep = sep ?? "";
|
|
||||||
return s.repeat(n) + sep;
|
|
||||||
}),
|
|
||||||
reverse: new LuaBuiltinFunction((s: string) => {
|
|
||||||
return s.split("").reverse().join("");
|
|
||||||
}),
|
|
||||||
sub: new LuaBuiltinFunction((s: string, i: number, j?: number) => {
|
|
||||||
j = j ?? s.length;
|
|
||||||
return s.slice(i - 1, j);
|
|
||||||
}),
|
|
||||||
split: new LuaBuiltinFunction((s: string, sep: string) => {
|
|
||||||
return s.split(sep);
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
import {
|
import {
|
||||||
type ILuaFunction,
|
type ILuaFunction,
|
||||||
LuaBuiltinFunction,
|
LuaBuiltinFunction,
|
||||||
LuaTable,
|
LuaTable,
|
||||||
} from "$common/space_lua/runtime.ts";
|
} from "$common/space_lua/runtime.ts";
|
||||||
|
|
||||||
export const tableApi = new LuaTable({
|
export const tableApi = new LuaTable({
|
||||||
concat: new LuaBuiltinFunction(
|
concat: new LuaBuiltinFunction(
|
||||||
(tbl: LuaTable, sep?: string, i?: number, j?: number) => {
|
(tbl: LuaTable, sep?: string, i?: number, j?: number) => {
|
||||||
sep = sep ?? "";
|
sep = sep ?? "";
|
||||||
i = i ?? 1;
|
i = i ?? 1;
|
||||||
j = j ?? tbl.length;
|
j = j ?? tbl.length;
|
||||||
const result = [];
|
const result = [];
|
||||||
for (let k = i; k <= j; k++) {
|
for (let k = i; k <= j; k++) {
|
||||||
result.push(tbl.get(k));
|
result.push(tbl.get(k));
|
||||||
}
|
}
|
||||||
return result.join(sep);
|
return result.join(sep);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
insert: new LuaBuiltinFunction(
|
insert: new LuaBuiltinFunction(
|
||||||
(tbl: LuaTable, posOrValue: number | any, value?: any) => {
|
(tbl: LuaTable, posOrValue: number | any, value?: any) => {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
value = posOrValue;
|
value = posOrValue;
|
||||||
posOrValue = tbl.length + 1;
|
posOrValue = tbl.length + 1;
|
||||||
}
|
}
|
||||||
tbl.insert(posOrValue, value);
|
tbl.insert(posOrValue, value);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
remove: new LuaBuiltinFunction((tbl: LuaTable, pos?: number) => {
|
remove: new LuaBuiltinFunction((tbl: LuaTable, pos?: number) => {
|
||||||
pos = pos ?? tbl.length;
|
pos = pos ?? tbl.length;
|
||||||
tbl.remove(pos);
|
tbl.remove(pos);
|
||||||
}),
|
}),
|
||||||
sort: new LuaBuiltinFunction((tbl: LuaTable, comp?: ILuaFunction) => {
|
sort: new LuaBuiltinFunction((tbl: LuaTable, comp?: ILuaFunction) => {
|
||||||
return tbl.sort(comp);
|
return tbl.sort(comp);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
|
@ -2,9 +2,9 @@ import type { SysCallMapping } from "$lib/plugos/system.ts";
|
||||||
import { parse } from "../space_lua/parse.ts";
|
import { parse } from "../space_lua/parse.ts";
|
||||||
|
|
||||||
export function luaSyscalls(): SysCallMapping {
|
export function luaSyscalls(): SysCallMapping {
|
||||||
return {
|
return {
|
||||||
"lua.parse": (_ctx, code: string) => {
|
"lua.parse": (_ctx, code: string) => {
|
||||||
return parse(code);
|
return parse(code);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { syscall } from "../syscall.ts";
|
||||||
import type { ParseTree } from "../lib/tree.ts";
|
import type { ParseTree } from "../lib/tree.ts";
|
||||||
|
|
||||||
export function parse(
|
export function parse(
|
||||||
code: string,
|
code: string,
|
||||||
): Promise<ParseTree> {
|
): Promise<ParseTree> {
|
||||||
return syscall("lua.parse", code);
|
return syscall("lua.parse", code);
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,4 +106,4 @@ config:
|
||||||
nullable: true
|
nullable: true
|
||||||
spaceIgnore:
|
spaceIgnore:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
|
|
|
@ -44,7 +44,7 @@ functions:
|
||||||
command:
|
command:
|
||||||
name: "Space: Reindex"
|
name: "Space: Reindex"
|
||||||
requireMode: rw
|
requireMode: rw
|
||||||
|
|
||||||
processIndexQueue:
|
processIndexQueue:
|
||||||
path: ./command.ts:processIndexQueue
|
path: ./command.ts:processIndexQueue
|
||||||
mqSubscriptions:
|
mqSubscriptions:
|
||||||
|
@ -242,7 +242,7 @@ functions:
|
||||||
events:
|
events:
|
||||||
- query:command
|
- query:command
|
||||||
config:
|
config:
|
||||||
# Schema for the built-in tags indexed by this plug
|
# Schema for the built-in tags indexed by this plug
|
||||||
schema.tag:
|
schema.tag:
|
||||||
page:
|
page:
|
||||||
type: object
|
type: object
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
name: search
|
name: search
|
||||||
functions:
|
functions:
|
||||||
indexPage:
|
indexPage:
|
||||||
path: search.ts:indexPage
|
path: search.ts:indexPage
|
||||||
events:
|
events:
|
||||||
- page:index
|
- page:index
|
||||||
|
|
||||||
searchQueryProvider:
|
searchQueryProvider:
|
||||||
path: ./search.ts:queryProvider
|
path: ./search.ts:queryProvider
|
||||||
|
@ -32,4 +32,4 @@ functions:
|
||||||
path: ./search.ts:getFileMetaSearch
|
path: ./search.ts:getFileMetaSearch
|
||||||
pageNamespace:
|
pageNamespace:
|
||||||
pattern: "🔍 .+"
|
pattern: "🔍 .+"
|
||||||
operation: getFileMeta
|
operation: getFileMeta
|
||||||
|
|
|
@ -10,24 +10,24 @@ functions:
|
||||||
clipboardShareOptions:
|
clipboardShareOptions:
|
||||||
path: share.ts:clipboardShareOptions
|
path: share.ts:clipboardShareOptions
|
||||||
events:
|
events:
|
||||||
- share:options
|
- share:options
|
||||||
|
|
||||||
clipboardMarkdownShare:
|
clipboardMarkdownShare:
|
||||||
path: share.ts:clipboardMarkdownShare
|
path: share.ts:clipboardMarkdownShare
|
||||||
events:
|
events:
|
||||||
- share:clean-markdown
|
- share:clean-markdown
|
||||||
|
|
||||||
clipboardRichTextShare:
|
clipboardRichTextShare:
|
||||||
path: share.ts:clipboardRichTextShare
|
path: share.ts:clipboardRichTextShare
|
||||||
events:
|
events:
|
||||||
- share:rich-text
|
- share:rich-text
|
||||||
|
|
||||||
publishShareOptions:
|
publishShareOptions:
|
||||||
path: publish.ts:publishShareOptions
|
path: publish.ts:publishShareOptions
|
||||||
events:
|
events:
|
||||||
- share:options
|
- share:options
|
||||||
|
|
||||||
publishShare:
|
publishShare:
|
||||||
path: publish.ts:publishShare
|
path: publish.ts:publishShare
|
||||||
events:
|
events:
|
||||||
- share:publish
|
- share:publish
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ functions:
|
||||||
indexTasks:
|
indexTasks:
|
||||||
path: "./task.ts:indexTasks"
|
path: "./task.ts:indexTasks"
|
||||||
events:
|
events:
|
||||||
- page:index
|
- page:index
|
||||||
taskToggle:
|
taskToggle:
|
||||||
path: "./task.ts:taskToggle"
|
path: "./task.ts:taskToggle"
|
||||||
events:
|
events:
|
||||||
|
@ -34,7 +34,7 @@ functions:
|
||||||
taskComplete:
|
taskComplete:
|
||||||
path: ./complete.ts:completeTaskState
|
path: ./complete.ts:completeTaskState
|
||||||
events:
|
events:
|
||||||
- editor:complete
|
- editor:complete
|
||||||
|
|
||||||
removeCompletedTasksCommand:
|
removeCompletedTasksCommand:
|
||||||
path: task.ts:removeCompletedTasksCommand
|
path: task.ts:removeCompletedTasksCommand
|
||||||
|
@ -101,4 +101,4 @@ config:
|
||||||
state:
|
state:
|
||||||
type: string
|
type: string
|
||||||
count:
|
count:
|
||||||
type: number
|
type: number
|
||||||
|
|
200
web/auth.html
200
web/auth.html
|
@ -1,99 +1,129 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<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";
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
<head>
|
footer {
|
||||||
<meta charset="utf-8" />
|
margin-top: 10px;
|
||||||
<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";
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
footer {
|
header {
|
||||||
margin-top: 10px;
|
background-color: #e1e1e1;
|
||||||
}
|
border-bottom: #cacaca 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
header {
|
h1 {
|
||||||
background-color: #e1e1e1;
|
margin: 0;
|
||||||
border-bottom: #cacaca 1px solid;
|
margin: 0 auto;
|
||||||
}
|
max-width: 800px;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
form {
|
||||||
margin: 0;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 800px;
|
padding: 10px;
|
||||||
padding: 8px;
|
}
|
||||||
font-size: 28px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
input {
|
||||||
max-width: 800px;
|
font-size: 18px;
|
||||||
margin: 0 auto;
|
}
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
form > div {
|
||||||
font-size: 18px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
form>div {
|
.error-message {
|
||||||
margin-bottom: 5px;
|
color: red;
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
.error-message {
|
<body>
|
||||||
color: red;
|
<header>
|
||||||
}
|
<h1>
|
||||||
</style>
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
placeholder="Password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="submit" value="Login" />
|
||||||
|
</div>
|
||||||
|
<footer>
|
||||||
|
<a href="https://silverbullet.md">What is SilverBullet?</a>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
|
||||||
</head>
|
<script>
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
<body>
|
const error = params.get("error");
|
||||||
<header>
|
if (error === "0") {
|
||||||
<h1>Login to <img src="/.client/logo.png" style="height: 1ch;" /> SilverBullet</h1>
|
document.querySelector(".error-message").innerText =
|
||||||
</header>
|
"The sent data was invalid";
|
||||||
<form action="/.auth" method="POST" id="login">
|
} else if (error === "1") {
|
||||||
<div class="error-message"></div>
|
document.querySelector(".error-message").innerText =
|
||||||
<div>
|
"Invalid username or password";
|
||||||
<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" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input type="submit" value="Login" />
|
|
||||||
</div>
|
|
||||||
<footer>
|
|
||||||
<a href="https://silverbullet.md">What is SilverBullet?</a>
|
|
||||||
</footer>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script>
|
const from = params.get("from");
|
||||||
const params = new URLSearchParams(window.location.search);
|
if (from) {
|
||||||
|
var input = document.createElement("input");
|
||||||
|
input.setAttribute("type", "hidden");
|
||||||
|
input.setAttribute("name", "from");
|
||||||
|
input.setAttribute("value", from);
|
||||||
|
|
||||||
const error = params.get('error');
|
document.getElementById("login").appendChild(input);
|
||||||
if (error === "0") {
|
}
|
||||||
document.querySelector('.error-message').innerText = "The sent data was invalid";
|
</script>
|
||||||
} else if (error === "1") {
|
</body>
|
||||||
document.querySelector('.error-message').innerText = "Invalid username or password";
|
</html>
|
||||||
}
|
|
||||||
|
|
||||||
const from = params.get("from");
|
|
||||||
if (from) {
|
|
||||||
var input = document.createElement("input");
|
|
||||||
input.setAttribute("type", "hidden");
|
|
||||||
input.setAttribute("name", "from");
|
|
||||||
input.setAttribute("value", from);
|
|
||||||
|
|
||||||
document.getElementById("login").appendChild(input);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
147
web/index.html
147
web/index.html
|
@ -1,82 +1,89 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" prefix="og: https://ogp.me/ns#">
|
<html lang="en" prefix="og: https://ogp.me/ns#">
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
|
<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)"
|
||||||
|
/>
|
||||||
|
<meta name="referrer" content="no-referrer" />
|
||||||
|
|
||||||
<head>
|
<title>{{TITLE}}</title>
|
||||||
<meta charset="utf-8" />
|
<meta property="og:image" content="/.client/logo.png" />
|
||||||
<meta name="viewport"
|
<meta property="og:description" content="{{DESCRIPTION}}" />
|
||||||
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)">
|
|
||||||
<meta name="referrer" content="no-referrer" />
|
|
||||||
|
|
||||||
<title>{{TITLE}}</title>
|
<script>
|
||||||
<meta property="og:image" content="/.client/logo.png">
|
// Some global variables we need to make this work
|
||||||
<meta property="og:description" content={{DESCRIPTION}}>
|
Deno = {
|
||||||
|
args: [],
|
||||||
<script>
|
build: {
|
||||||
// Some global variables we need to make this work
|
arch: "x86_64",
|
||||||
Deno = {
|
os: "browser",
|
||||||
args: [],
|
|
||||||
build: {
|
|
||||||
arch: "x86_64",
|
|
||||||
os: "browser",
|
|
||||||
},
|
|
||||||
core: {
|
|
||||||
runMicrotasks() {
|
|
||||||
// console.log("Not supported");
|
|
||||||
},
|
},
|
||||||
setHasTickScheduled() {
|
core: {
|
||||||
// console.log("Not supported");
|
runMicrotasks() {
|
||||||
}
|
// console.log("Not supported");
|
||||||
},
|
},
|
||||||
env: {
|
setHasTickScheduled() {
|
||||||
get(key) {
|
// console.log("Not supported");
|
||||||
// return undefined;
|
},
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
get(key) {
|
||||||
|
// return undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
AlreadyExists: class extends Error {},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
errors: {
|
|
||||||
AlreadyExists: class extends Error { },
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.silverBulletConfig = {
|
|
||||||
// These {{VARIABLES}} are replaced by http_server.ts
|
|
||||||
spaceFolderPath: "{{SPACE_PATH}}",
|
|
||||||
syncOnly: "{{SYNC_ONLY}}" === "true",
|
|
||||||
enableSpaceScript: "{{ENABLE_SPACE_SCRIPT}}" === "true",
|
|
||||||
readOnly: "{{READ_ONLY}}" === "true",
|
|
||||||
};
|
|
||||||
// But in case these variables aren't replaced by the server, fall back sync only mode
|
|
||||||
if (window.silverBulletConfig.spaceFolderPath.includes("{{")) {
|
|
||||||
window.silverBulletConfig = {
|
|
||||||
spaceFolderPath: "",
|
|
||||||
syncOnly: true,
|
|
||||||
readOnly: false,
|
|
||||||
enableSpaceScripts: false,
|
|
||||||
};
|
};
|
||||||
}
|
window.silverBulletConfig = {
|
||||||
</script>
|
// These {{VARIABLES}} are replaced by http_server.ts
|
||||||
<link rel="stylesheet" href="/.client/main.css" />
|
spaceFolderPath: "{{SPACE_PATH}}",
|
||||||
<style id="custom-styles"></style>
|
syncOnly: "{{SYNC_ONLY}}" === "true",
|
||||||
<script type="module" src="/.client/client.js"></script>
|
enableSpaceScript: "{{ENABLE_SPACE_SCRIPT}}" === "true",
|
||||||
<link rel="manifest" href="/.client/manifest.json" />
|
readOnly: "{{READ_ONLY}}" === "true",
|
||||||
<link rel="icon" type="image/x-icon" href="/.client/favicon.png" />
|
};
|
||||||
</head>
|
// But in case these variables aren't replaced by the server, fall back sync only mode
|
||||||
|
if (window.silverBulletConfig.spaceFolderPath.includes("{{")) {
|
||||||
|
window.silverBulletConfig = {
|
||||||
|
spaceFolderPath: "",
|
||||||
|
syncOnly: true,
|
||||||
|
readOnly: false,
|
||||||
|
enableSpaceScripts: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<link rel="stylesheet" href="/.client/main.css" />
|
||||||
|
<style id="custom-styles"></style>
|
||||||
|
<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>
|
||||||
|
|
||||||
<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">
|
<div class="cm-content">
|
||||||
<div class="cm-content">
|
{{CONTENT}}
|
||||||
{{CONTENT}}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</body>
|
||||||
</body>
|
</html>
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
font-family: var(--editor-font);
|
font-family: var(--editor-font);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-notifications>div {
|
.sb-notifications > div {
|
||||||
border: var(--notifications-border-color) 1px solid;
|
border: var(--notifications-border-color) 1px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -424,7 +428,7 @@ a,
|
||||||
}
|
}
|
||||||
|
|
||||||
a.sb-wiki-link-page-missing,
|
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);
|
color: var(--editor-wiki-link-page-missing-color);
|
||||||
background-color: var(--editor-wiki-link-page-background-color);
|
background-color: var(--editor-wiki-link-page-background-color);
|
||||||
}
|
}
|
||||||
|
@ -443,4 +447,4 @@ a.sb-wiki-link-page-missing,
|
||||||
|
|
||||||
.sb-line-comment {
|
.sb-line-comment {
|
||||||
background-color: var(--editor-code-comment-color); // rgba(255, 255, 0, 0.5);
|
background-color: var(--editor-code-comment-color); // rgba(255, 255, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
|
@ -264,7 +264,7 @@
|
||||||
text-indent: -7ch;
|
text-indent: -7ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-checkbox>input[type="checkbox"] {
|
.sb-checkbox > input[type="checkbox"] {
|
||||||
width: 3ch;
|
width: 3ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,7 +339,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
a.sb-wiki-link-page-missing,
|
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;
|
border-radius: 5px;
|
||||||
padding: 0 5px;
|
padding: 0 5px;
|
||||||
// white-space: nowrap;
|
// white-space: nowrap;
|
||||||
|
@ -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;
|
||||||
|
@ -439,7 +438,7 @@
|
||||||
margin: 0 3px;
|
margin: 0 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sb-code-copy-button>svg {
|
.sb-code-copy-button > svg {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -592,7 +591,6 @@
|
||||||
button:last-of-type {
|
button:last-of-type {
|
||||||
margin-right: 2px;
|
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;
|
margin-left: -1ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -650,4 +648,4 @@
|
||||||
|
|
||||||
div:not(.cm-focused).cm-fat-cursor {
|
div:not(.cm-focused).cm-fat-cursor {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,15 +84,13 @@ body {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
|
||||||
>div {
|
> div {
|
||||||
padding: 3px;
|
padding: 3px;
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
@ -271,4 +279,4 @@ html[data-theme="dark"] {
|
||||||
|
|
||||||
--admonition-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>');
|
--admonition-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path><line x1="12" y1="9" x2="12" y2="13"></line><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>');
|
||||||
--admonition-color: #ff9100;
|
--admonition-color: #ff9100;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue