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
@ -20,7 +19,7 @@ tasks:
deno task install
gp sync-done setup
- name: Server watcher
init: |
init: |
gp sync-await setup
mkdir pages
command: deno task watch-server pages
@ -33,4 +32,4 @@ tasks:
vscode:
extensions:
- denoland.vscode-deno
- denoland.vscode-deno

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
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

@ -5,38 +5,38 @@ import { evalStatement } from "$common/space_lua/eval.ts";
import { assert } from "@std/assert/assert";
Deno.test("Lua language tests", async () => {
// Read the Lua file
const luaFile = await Deno.readTextFile(
new URL("./language_test.lua", import.meta.url).pathname,
);
const chunk = parse(luaFile, {});
const env = new LuaEnv(luaBuildStandardEnv());
// Read the Lua file
const luaFile = await Deno.readTextFile(
new URL("./language_test.lua", import.meta.url).pathname,
);
const chunk = parse(luaFile, {});
const env = new LuaEnv(luaBuildStandardEnv());
try {
await evalStatement(chunk, env);
} catch (e: any) {
console.error(`Error evaluating script:`, toPrettyString(e, luaFile));
assert(false);
}
try {
await evalStatement(chunk, env);
} catch (e: any) {
console.error(`Error evaluating script:`, toPrettyString(e, luaFile));
assert(false);
}
});
function toPrettyString(err: LuaRuntimeError, code: string): string {
if (!err.context || !err.context.from || !err.context.to) {
return err.toString();
if (!err.context || !err.context.from || !err.context.to) {
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
let line = 1;
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)
}`;
}
return `LuaRuntimeError: ${err.message} at ${line}:${column}:\n ${
code.substring(from, err.context.to)
}`;
}

View File

@ -1,44 +1,44 @@
import { assertEquals } from "@std/assert/equals";
import {
jsToLuaValue,
luaLen,
LuaMultiRes,
jsToLuaValue,
luaLen,
LuaMultiRes,
} from "$common/space_lua/runtime.ts";
Deno.test("Test Lua Rutime", () => {
// Test LuaMultires
assertEquals(new LuaMultiRes([]).flatten().values, []);
assertEquals(new LuaMultiRes([1, 2, 3]).flatten().values, [1, 2, 3]);
assertEquals(
new LuaMultiRes([1, new LuaMultiRes([2, 3])]).flatten().values,
[
1,
2,
3,
],
);
// Test LuaMultires
assertEquals(new LuaMultiRes([]).flatten().values, []);
assertEquals(new LuaMultiRes([1, 2, 3]).flatten().values, [1, 2, 3]);
assertEquals(
new LuaMultiRes([1, new LuaMultiRes([2, 3])]).flatten().values,
[
1,
2,
3,
],
);
// Test JavaScript to Lua conversion
assertEquals(jsToLuaValue(1), 1);
assertEquals(jsToLuaValue("hello"), "hello");
// Arrays
let luaVal = jsToLuaValue([1, 2, 3]);
assertEquals(luaLen(luaVal), 3);
assertEquals(luaVal.get(1), 1);
// Objects
luaVal = jsToLuaValue({ name: "Pete", age: 10 });
assertEquals(luaVal.get("name"), "Pete");
assertEquals(luaVal.get("age"), 10);
// Nested objects
luaVal = jsToLuaValue({ name: "Pete", list: [1, 2, 3] });
assertEquals(luaVal.get("name"), "Pete");
assertEquals(luaLen(luaVal.get("list")), 3);
assertEquals(luaVal.get("list").get(2), 2);
luaVal = jsToLuaValue([{ name: "Pete" }, { name: "John" }]);
assertEquals(luaLen(luaVal), 2);
assertEquals(luaVal.get(1).get("name"), "Pete");
assertEquals(luaVal.get(2).get("name"), "John");
// Functions in objects
luaVal = jsToLuaValue({ name: "Pete", first: (l: any[]) => l[0] });
assertEquals(luaVal.get("first").call([1, 2, 3]), 1);
// Test JavaScript to Lua conversion
assertEquals(jsToLuaValue(1), 1);
assertEquals(jsToLuaValue("hello"), "hello");
// Arrays
let luaVal = jsToLuaValue([1, 2, 3]);
assertEquals(luaLen(luaVal), 3);
assertEquals(luaVal.get(1), 1);
// Objects
luaVal = jsToLuaValue({ name: "Pete", age: 10 });
assertEquals(luaVal.get("name"), "Pete");
assertEquals(luaVal.get("age"), 10);
// Nested objects
luaVal = jsToLuaValue({ name: "Pete", list: [1, 2, 3] });
assertEquals(luaVal.get("name"), "Pete");
assertEquals(luaLen(luaVal.get("list")), 3);
assertEquals(luaVal.get("list").get(2), 2);
luaVal = jsToLuaValue([{ name: "Pete" }, { name: "John" }]);
assertEquals(luaLen(luaVal), 2);
assertEquals(luaVal.get(1).get("name"), "Pete");
assertEquals(luaVal.get(2).get("name"), "John");
// Functions in objects
luaVal = jsToLuaValue({ name: "Pete", first: (l: any[]) => l[0] });
assertEquals(luaVal.get("first").call([1, 2, 3]), 1);
});

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

@ -1,12 +1,12 @@
import {
type ILuaFunction,
LuaBuiltinFunction,
LuaEnv,
LuaMultiRes,
type LuaTable,
luaToString,
luaTypeOf,
type LuaValue,
type ILuaFunction,
LuaBuiltinFunction,
LuaEnv,
LuaMultiRes,
type LuaTable,
luaToString,
luaTypeOf,
type LuaValue,
} from "$common/space_lua/runtime.ts";
import { stringApi } from "$common/space_lua/stdlib/string.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";
const printFunction = new LuaBuiltinFunction((...args) => {
console.log("[Lua]", ...args.map(luaToString));
console.log("[Lua]", ...args.map(luaToString));
});
const assertFunction = new LuaBuiltinFunction(
async (value: any, message?: string) => {
if (!await value) {
throw new Error(`Assertion failed: ${message}`);
}
},
async (value: any, message?: string) => {
if (!await value) {
throw new Error(`Assertion failed: ${message}`);
}
},
);
const ipairsFunction = new LuaBuiltinFunction((ar: LuaTable) => {
let i = 1;
return () => {
if (i > ar.length) {
return;
}
const result = new LuaMultiRes([i, ar.get(i)]);
i++;
return result;
};
let i = 1;
return () => {
if (i > ar.length) {
return;
}
const result = new LuaMultiRes([i, ar.get(i)]);
i++;
return result;
};
});
const pairsFunction = new LuaBuiltinFunction((t: LuaTable) => {
const keys = t.keys();
let i = 0;
return () => {
if (i >= keys.length) {
return;
}
const key = keys[i];
i++;
return new LuaMultiRes([key, t.get(key)]);
};
const keys = t.keys();
let i = 0;
return () => {
if (i >= keys.length) {
return;
}
const key = keys[i];
i++;
return new LuaMultiRes([key, t.get(key)]);
};
});
const unpackFunction = new LuaBuiltinFunction((t: LuaTable) => {
const values: LuaValue[] = [];
for (let i = 1; i <= t.length; i++) {
values.push(t.get(i));
}
return new LuaMultiRes(values);
const values: LuaValue[] = [];
for (let i = 1; i <= t.length; i++) {
values.push(t.get(i));
}
return new LuaMultiRes(values);
});
const typeFunction = new LuaBuiltinFunction((value: LuaValue): string => {
return luaTypeOf(value);
return luaTypeOf(value);
});
const tostringFunction = new LuaBuiltinFunction((value: any) => {
return luaToString(value);
return luaToString(value);
});
const tonumberFunction = new LuaBuiltinFunction((value: LuaValue) => {
return Number(value);
return Number(value);
});
const errorFunction = new LuaBuiltinFunction((message: string) => {
throw new Error(message);
throw new Error(message);
});
const pcallFunction = new LuaBuiltinFunction(
async (fn: ILuaFunction, ...args) => {
try {
return new LuaMultiRes([true, await fn.call(...args)]);
} catch (e: any) {
return new LuaMultiRes([false, e.message]);
}
},
async (fn: ILuaFunction, ...args) => {
try {
return new LuaMultiRes([true, await fn.call(...args)]);
} catch (e: any) {
return new LuaMultiRes([false, e.message]);
}
},
);
const xpcallFunction = new LuaBuiltinFunction(
async (fn: ILuaFunction, errorHandler: ILuaFunction, ...args) => {
try {
return new LuaMultiRes([true, await fn.call(...args)]);
} catch (e: any) {
return new LuaMultiRes([false, await errorHandler.call(e.message)]);
}
},
async (fn: ILuaFunction, errorHandler: ILuaFunction, ...args) => {
try {
return new LuaMultiRes([true, await fn.call(...args)]);
} catch (e: any) {
return new LuaMultiRes([false, await errorHandler.call(e.message)]);
}
},
);
const setmetatableFunction = new LuaBuiltinFunction(
(table: LuaTable, metatable: LuaTable) => {
table.metatable = metatable;
return table;
},
(table: LuaTable, metatable: LuaTable) => {
table.metatable = metatable;
return table;
},
);
const rawsetFunction = new LuaBuiltinFunction(
(table: LuaTable, key: LuaValue, value: LuaValue) => {
table.rawSet(key, value);
return table;
},
(table: LuaTable, key: LuaValue, value: LuaValue) => {
table.rawSet(key, value);
return table;
},
);
const getmetatableFunction = new LuaBuiltinFunction((table: LuaTable) => {
return table.metatable;
return table.metatable;
});
export function luaBuildStandardEnv() {
const env = new LuaEnv();
// Top-level builtins
env.set("print", printFunction);
env.set("assert", assertFunction);
env.set("type", typeFunction);
env.set("tostring", tostringFunction);
env.set("tonumber", tonumberFunction);
env.set("unpack", unpackFunction);
// Iterators
env.set("pairs", pairsFunction);
env.set("ipairs", ipairsFunction);
// meta table stuff
env.set("setmetatable", setmetatableFunction);
env.set("getmetatable", getmetatableFunction);
env.set("rawset", rawsetFunction);
// Error handling
env.set("error", errorFunction);
env.set("pcall", pcallFunction);
env.set("xpcall", xpcallFunction);
const env = new LuaEnv();
// Top-level builtins
env.set("print", printFunction);
env.set("assert", assertFunction);
env.set("type", typeFunction);
env.set("tostring", tostringFunction);
env.set("tonumber", tonumberFunction);
env.set("unpack", unpackFunction);
// Iterators
env.set("pairs", pairsFunction);
env.set("ipairs", ipairsFunction);
// meta table stuff
env.set("setmetatable", setmetatableFunction);
env.set("getmetatable", getmetatableFunction);
env.set("rawset", rawsetFunction);
// Error handling
env.set("error", errorFunction);
env.set("pcall", pcallFunction);
env.set("xpcall", xpcallFunction);
// APIs
env.set("string", stringApi);
env.set("table", tableApi);
env.set("os", osApi);
env.set("js", jsApi);
return env;
// APIs
env.set("string", stringApi);
env.set("table", tableApi);
env.set("os", osApi);
env.set("js", jsApi);
return env;
}

View File

@ -1,33 +1,33 @@
import {
jsToLuaValue,
LuaBuiltinFunction,
LuaTable,
luaValueToJS,
jsToLuaValue,
LuaBuiltinFunction,
LuaTable,
luaValueToJS,
} from "$common/space_lua/runtime.ts";
export const jsApi = new LuaTable({
new: new LuaBuiltinFunction(
(constructorFn: any, ...args) => {
return new constructorFn(
...args.map(luaValueToJS),
);
},
),
importModule: new LuaBuiltinFunction((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.
*/
bind: new LuaBuiltinFunction((fn: any, obj: any, ...args: any[]) => {
return fn.bind(obj, ...args);
}),
tolua: new LuaBuiltinFunction(jsToLuaValue),
tojs: new LuaBuiltinFunction(luaValueToJS),
log: new LuaBuiltinFunction((...args) => {
console.log(...args);
}),
// assignGlobal: new LuaBuiltinFunction((name: string, value: any) => {
// (globalThis as any)[name] = value;
// }),
new: new LuaBuiltinFunction(
(constructorFn: any, ...args) => {
return new constructorFn(
...args.map(luaValueToJS),
);
},
),
importModule: new LuaBuiltinFunction((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.
*/
bind: new LuaBuiltinFunction((fn: any, obj: any, ...args: any[]) => {
return fn.bind(obj, ...args);
}),
tolua: new LuaBuiltinFunction(jsToLuaValue),
tojs: new LuaBuiltinFunction(luaValueToJS),
log: new LuaBuiltinFunction((...args) => {
console.log(...args);
}),
// assignGlobal: new LuaBuiltinFunction((name: string, value: any) => {
// (globalThis as any)[name] = value;
// }),
});

View File

@ -1,103 +1,102 @@
import { LuaBuiltinFunction, LuaTable } from "$common/space_lua/runtime.ts";
export const osApi = new LuaTable({
time: new LuaBuiltinFunction((tbl?: LuaTable) => {
if (tbl) {
// Build a date object from the table
const date = new Date();
if (!tbl.has("year")) {
throw new Error("time(): year is required");
}
date.setFullYear(tbl.get("year"));
if (!tbl.has("month")) {
throw new Error("time(): month is required");
}
date.setMonth(tbl.get("month") - 1);
if (!tbl.has("day")) {
throw new Error("time(): day is required");
}
date.setDate(tbl.get("day"));
date.setHours(tbl.get("hour") ?? 12);
date.setMinutes(tbl.get("min") ?? 0);
date.setSeconds(tbl.get("sec") ?? 0);
return Math.floor(date.getTime() / 1000);
} else {
return Math.floor(Date.now() / 1000);
}
}),
/**
* 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 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 (112), day (131), hour (023), min (059), sec (061, due to leap seconds), wday (weekday, 17, Sunday is 1), yday (day of the year, 1366), 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 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) => {
const date = timestamp ? new Date(timestamp * 1000) : new Date();
time: new LuaBuiltinFunction((tbl?: LuaTable) => {
if (tbl) {
// Build a date object from the table
const date = new Date();
if (!tbl.has("year")) {
throw new Error("time(): year is required");
}
date.setFullYear(tbl.get("year"));
if (!tbl.has("month")) {
throw new Error("time(): month is required");
}
date.setMonth(tbl.get("month") - 1);
if (!tbl.has("day")) {
throw new Error("time(): day is required");
}
date.setDate(tbl.get("day"));
date.setHours(tbl.get("hour") ?? 12);
date.setMinutes(tbl.get("min") ?? 0);
date.setSeconds(tbl.get("sec") ?? 0);
return Math.floor(date.getTime() / 1000);
} else {
return Math.floor(Date.now() / 1000);
}
}),
/**
* 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 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 (112), day (131), hour (023), min (059), sec (061, due to leap seconds), wday (weekday, 17, Sunday is 1), yday (day of the year, 1366), 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 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) => {
const date = timestamp ? new Date(timestamp * 1000) : new Date();
// Default Lua-like format when no format string is provided
if (!format) {
return date.toDateString() + " " + date.toLocaleTimeString();
}
// Default Lua-like format when no format string is provided
if (!format) {
return date.toDateString() + " " + date.toLocaleTimeString();
}
// Define mappings for Lua-style placeholders
const formatMap: { [key: string]: () => string } = {
// Year
"%Y": () => date.getFullYear().toString(),
"%y": () => (date.getFullYear() % 100).toString().padStart(2, "0"),
// Month
"%m": () => (date.getMonth() + 1).toString().padStart(2, "0"),
"%b": () => date.toLocaleString("en-US", { month: "short" }),
"%B": () => date.toLocaleString("en-US", { month: "long" }),
// Day
"%d": () => date.getDate().toString().padStart(2, "0"),
"%e": () => date.getDate().toString(),
// Hour
"%H": () => date.getHours().toString().padStart(2, "0"),
"%I": () =>
(date.getHours() % 12 || 12).toString().padStart(2, "0"),
// Minute
"%M": () => date.getMinutes().toString().padStart(2, "0"),
// Second
"%S": () => date.getSeconds().toString().padStart(2, "0"),
// AM/PM
"%p": () => date.getHours() >= 12 ? "PM" : "AM",
// Day of the week
"%A": () => date.toLocaleString("en-US", { weekday: "long" }),
"%a": () => date.toLocaleString("en-US", { weekday: "short" }),
"%w": () => date.getDay().toString(),
// Day of the year
"%j": () => {
const start = new Date(date.getFullYear(), 0, 0);
const diff = date.getTime() - start.getTime();
const oneDay = 1000 * 60 * 60 * 24;
const dayOfYear = Math.floor(diff / oneDay);
return dayOfYear.toString().padStart(3, "0");
},
// Time zone
"%Z": () => {
const match = date.toTimeString().match(/\((.*)\)/);
return match ? match[1] : "";
},
"%z": () => {
const offset = -date.getTimezoneOffset();
const sign = offset >= 0 ? "+" : "-";
const absOffset = Math.abs(offset);
const hours = Math.floor(absOffset / 60).toString().padStart(
2,
"0",
);
const minutes = (absOffset % 60).toString().padStart(2, "0");
return `${sign}${hours}${minutes}`;
},
// Literal %
"%%": () => "%",
};
// Define mappings for Lua-style placeholders
const formatMap: { [key: string]: () => string } = {
// Year
"%Y": () => date.getFullYear().toString(),
"%y": () => (date.getFullYear() % 100).toString().padStart(2, "0"),
// Month
"%m": () => (date.getMonth() + 1).toString().padStart(2, "0"),
"%b": () => date.toLocaleString("en-US", { month: "short" }),
"%B": () => date.toLocaleString("en-US", { month: "long" }),
// Day
"%d": () => date.getDate().toString().padStart(2, "0"),
"%e": () => date.getDate().toString(),
// Hour
"%H": () => date.getHours().toString().padStart(2, "0"),
"%I": () => (date.getHours() % 12 || 12).toString().padStart(2, "0"),
// Minute
"%M": () => date.getMinutes().toString().padStart(2, "0"),
// Second
"%S": () => date.getSeconds().toString().padStart(2, "0"),
// AM/PM
"%p": () => date.getHours() >= 12 ? "PM" : "AM",
// Day of the week
"%A": () => date.toLocaleString("en-US", { weekday: "long" }),
"%a": () => date.toLocaleString("en-US", { weekday: "short" }),
"%w": () => date.getDay().toString(),
// Day of the year
"%j": () => {
const start = new Date(date.getFullYear(), 0, 0);
const diff = date.getTime() - start.getTime();
const oneDay = 1000 * 60 * 60 * 24;
const dayOfYear = Math.floor(diff / oneDay);
return dayOfYear.toString().padStart(3, "0");
},
// Time zone
"%Z": () => {
const match = date.toTimeString().match(/\((.*)\)/);
return match ? match[1] : "";
},
"%z": () => {
const offset = -date.getTimezoneOffset();
const sign = offset >= 0 ? "+" : "-";
const absOffset = Math.abs(offset);
const hours = Math.floor(absOffset / 60).toString().padStart(
2,
"0",
);
const minutes = (absOffset % 60).toString().padStart(2, "0");
return `${sign}${hours}${minutes}`;
},
// Literal %
"%%": () => "%",
};
// Replace format placeholders with corresponding values
return format.replace(/%[A-Za-z%]/g, (match) => {
const formatter = formatMap[match];
return formatter ? formatter() : match;
});
}),
// Replace format placeholders with corresponding values
return format.replace(/%[A-Za-z%]/g, (match) => {
const formatter = formatMap[match];
return formatter ? formatter() : match;
});
}),
});

View File

@ -1,106 +1,106 @@
import {
LuaBuiltinFunction,
LuaMultiRes,
LuaTable,
luaToString,
LuaBuiltinFunction,
LuaMultiRes,
LuaTable,
luaToString,
} from "$common/space_lua/runtime.ts";
export const stringApi = new LuaTable({
byte: new LuaBuiltinFunction((s: string, i?: number, j?: number) => {
i = i ?? 1;
j = j ?? i;
const result = [];
for (let k = i; k <= j; k++) {
result.push(s.charCodeAt(k - 1));
byte: new LuaBuiltinFunction((s: string, i?: number, j?: number) => {
i = i ?? 1;
j = j ?? i;
const result = [];
for (let k = i; k <= j; k++) {
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);
}),
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;
}
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);
}),
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);
}),
});

View File

@ -1,36 +1,36 @@
import {
type ILuaFunction,
LuaBuiltinFunction,
LuaTable,
type ILuaFunction,
LuaBuiltinFunction,
LuaTable,
} from "$common/space_lua/runtime.ts";
export const tableApi = new LuaTable({
concat: new LuaBuiltinFunction(
(tbl: LuaTable, sep?: string, i?: number, j?: number) => {
sep = sep ?? "";
i = i ?? 1;
j = j ?? tbl.length;
const result = [];
for (let k = i; k <= j; k++) {
result.push(tbl.get(k));
}
return result.join(sep);
},
),
insert: new LuaBuiltinFunction(
(tbl: LuaTable, posOrValue: number | any, value?: any) => {
if (value === undefined) {
value = posOrValue;
posOrValue = tbl.length + 1;
}
tbl.insert(posOrValue, value);
},
),
remove: new LuaBuiltinFunction((tbl: LuaTable, pos?: number) => {
pos = pos ?? tbl.length;
tbl.remove(pos);
}),
sort: new LuaBuiltinFunction((tbl: LuaTable, comp?: ILuaFunction) => {
return tbl.sort(comp);
}),
concat: new LuaBuiltinFunction(
(tbl: LuaTable, sep?: string, i?: number, j?: number) => {
sep = sep ?? "";
i = i ?? 1;
j = j ?? tbl.length;
const result = [];
for (let k = i; k <= j; k++) {
result.push(tbl.get(k));
}
return result.join(sep);
},
),
insert: new LuaBuiltinFunction(
(tbl: LuaTable, posOrValue: number | any, value?: any) => {
if (value === undefined) {
value = posOrValue;
posOrValue = tbl.length + 1;
}
tbl.insert(posOrValue, value);
},
),
remove: new LuaBuiltinFunction((tbl: LuaTable, pos?: number) => {
pos = pos ?? tbl.length;
tbl.remove(pos);
}),
sort: new LuaBuiltinFunction((tbl: LuaTable, comp?: ILuaFunction) => {
return tbl.sort(comp);
}),
});

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

@ -2,9 +2,9 @@ import type { SysCallMapping } from "$lib/plugos/system.ts";
import { parse } from "../space_lua/parse.ts";
export function luaSyscalls(): SysCallMapping {
return {
"lua.parse": (_ctx, code: string) => {
return parse(code);
},
};
return {
"lua.parse": (_ctx, code: string) => {
return parse(code);
},
};
}

View File

@ -2,7 +2,7 @@ import { syscall } from "../syscall.ts";
import type { ParseTree } from "../lib/tree.ts";
export function parse(
code: string,
code: string,
): Promise<ParseTree> {
return syscall("lua.parse", code);
return syscall("lua.parse", code);
}

View File

@ -106,4 +106,4 @@ config:
nullable: true
spaceIgnore:
type: string
nullable: true
nullable: true

View File

@ -44,7 +44,7 @@ functions:
command:
name: "Space: Reindex"
requireMode: rw
processIndexQueue:
path: ./command.ts:processIndexQueue
mqSubscriptions:
@ -242,7 +242,7 @@ functions:
events:
- query:command
config:
# Schema for the built-in tags indexed by this plug
# Schema for the built-in tags indexed by this plug
schema.tag:
page:
type: object

View File

@ -1,9 +1,9 @@
name: search
functions:
indexPage:
path: search.ts:indexPage
events:
- page:index
path: search.ts:indexPage
events:
- page:index
searchQueryProvider:
path: ./search.ts:queryProvider
@ -32,4 +32,4 @@ functions:
path: ./search.ts:getFileMetaSearch
pageNamespace:
pattern: "🔍 .+"
operation: getFileMeta
operation: getFileMeta

View File

@ -10,24 +10,24 @@ functions:
clipboardShareOptions:
path: share.ts:clipboardShareOptions
events:
- share:options
- share:options
clipboardMarkdownShare:
path: share.ts:clipboardMarkdownShare
events:
- share:clean-markdown
- share:clean-markdown
clipboardRichTextShare:
path: share.ts:clipboardRichTextShare
events:
- share:rich-text
- share:rich-text
publishShareOptions:
path: publish.ts:publishShareOptions
events:
- share:options
- share:options
publishShare:
path: publish.ts:publishShare
events:
- share:publish
- share:publish

View File

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

View File

@ -7,7 +7,7 @@ functions:
indexTasks:
path: "./task.ts:indexTasks"
events:
- page:index
- page:index
taskToggle:
path: "./task.ts:taskToggle"
events:
@ -34,7 +34,7 @@ functions:
taskComplete:
path: ./complete.ts:completeTaskState
events:
- editor:complete
- editor:complete
removeCompletedTasksCommand:
path: task.ts:removeCompletedTasksCommand
@ -101,4 +101,4 @@ config:
state:
type: string
count:
type: number
type: number

View File

@ -1,99 +1,129 @@
<!DOCTYPE html>
<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>
<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;
}
footer {
margin-top: 10px;
}
footer {
margin-top: 10px;
}
header {
background-color: #e1e1e1;
border-bottom: #cacaca 1px solid;
}
header {
background-color: #e1e1e1;
border-bottom: #cacaca 1px solid;
}
h1 {
margin: 0;
margin: 0 auto;
max-width: 800px;
padding: 8px;
font-size: 28px;
font-weight: normal;
}
h1 {
margin: 0;
margin: 0 auto;
max-width: 800px;
padding: 8px;
font-size: 28px;
font-weight: normal;
}
form {
max-width: 800px;
margin: 0 auto;
padding: 10px;
}
form {
max-width: 800px;
margin: 0 auto;
padding: 10px;
}
input {
font-size: 18px;
}
input {
font-size: 18px;
}
form > div {
margin-bottom: 5px;
}
form>div {
margin-bottom: 5px;
}
.error-message {
color: red;
}
</style>
</head>
.error-message {
color: red;
}
</style>
<body>
<header>
<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"
/>
</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>
<header>
<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" />
</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>
const error = params.get("error");
if (error === "0") {
document.querySelector(".error-message").innerText =
"The sent data was invalid";
} else if (error === "1") {
document.querySelector(".error-message").innerText =
"Invalid username or password";
}
<script>
const params = new URLSearchParams(window.location.search);
const from = params.get("from");
if (from) {
var input = document.createElement("input");
input.setAttribute("type", "hidden");
input.setAttribute("name", "from");
input.setAttribute("value", from);
const error = params.get('error');
if (error === "0") {
document.querySelector('.error-message').innerText = "The sent data was invalid";
} else if (error === "1") {
document.querySelector('.error-message').innerText = "Invalid username or password";
}
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>
document.getElementById("login").appendChild(input);
}
</script>
</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,82 +1,89 @@
<!DOCTYPE html>
<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>
<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" />
<title>{{TITLE}}</title>
<meta property="og:image" content="/.client/logo.png" />
<meta property="og:description" content="{{DESCRIPTION}}" />
<title>{{TITLE}}</title>
<meta property="og:image" content="/.client/logo.png">
<meta property="og:description" content={{DESCRIPTION}}>
<script>
// Some global variables we need to make this work
Deno = {
args: [],
build: {
arch: "x86_64",
os: "browser",
},
core: {
runMicrotasks() {
// console.log("Not supported");
<script>
// Some global variables we need to make this work
Deno = {
args: [],
build: {
arch: "x86_64",
os: "browser",
},
setHasTickScheduled() {
// console.log("Not supported");
}
},
env: {
get(key) {
// return undefined;
core: {
runMicrotasks() {
// console.log("Not supported");
},
setHasTickScheduled() {
// console.log("Not supported");
},
},
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,
};
}
</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>
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,
};
}
</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>
<div id="sb-root">
<div id="sb-main">
<div id="sb-editor">
<div class="cm-editor">
<div class="cm-content">
{{CONTENT}}
<body>
<div id="sb-root">
<div id="sb-main">
<div id="sb-editor">
<div class="cm-editor">
<div class="cm-content">
{{CONTENT}}
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
</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);
}
@ -443,4 +447,4 @@ a.sb-wiki-link-page-missing,
.sb-line-comment {
background-color: var(--editor-code-comment-color); // rgba(255, 255, 0, 0.5);
}
}

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;
}
@ -650,4 +648,4 @@
div:not(.cm-focused).cm-fat-cursor {
outline: none !important;
}
}

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;
@ -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-color: #ff9100;
}
}