From 8d3dc4cc4ea4e28605eb72a6699ba5e6c0aacbbd Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Thu, 12 Jun 2025 20:30:01 +0200 Subject: [PATCH] Refactor, cleanup --- build_web.ts | 1 + cmd/compile.ts | 1 + cmd/test/runtime.test.ts | 2 +- deno.json | 20 +- lib/README.md | 3 +- lib/data/datastore.test.ts | 4 +- lib/data/datastore.ts | 4 +- lib/data/indexeddb_kv_primitives.ts | 8 +- lib/data/kv_primitives.ts | 5 + lib/data/memory_kv_primitives.ts | 67 +-- lib/data/mq.datastore.ts | 9 +- lib/data/prefixed_kv_primitives.ts | 1 + lib/memory_cache.test.ts | 2 + lib/plugos/eventhook.ts | 1 + lib/plugos/hooks/mq.ts | 7 +- lib/plugos/sandboxes/no_sandbox.ts | 3 +- lib/plugos/sandboxes/sandbox.ts | 3 + lib/plugos/sandboxes/worker_sandbox.ts | 4 +- lib/plugos/system.ts | 2 +- lib/plugos/types.ts | 1 + lib/plugos/worker_runtime.ts | 1 + {web => lib}/space_lua/ast.ts | 0 {web => lib}/space_lua/eval.test.ts | 2 +- {web => lib}/space_lua/eval.ts | 27 +- {web => lib}/space_lua/language_core_test.lua | 0 {web => lib}/space_lua/lua.grammar | 0 {web => lib}/space_lua/lua.test.ts | 12 +- {web => lib}/space_lua/lume_test.lua | 0 {web => lib}/space_lua/parse-lua.js | 1 - {web => lib}/space_lua/parse-lua.terms.js | 0 {web => lib}/space_lua/parse.test.ts | 8 +- {web => lib}/space_lua/parse.ts | 5 +- .../space_lua/query_collection.test.ts | 8 +- {web => lib}/space_lua/query_collection.ts | 19 +- {web => lib}/space_lua/query_test.lua | 0 {web => lib}/space_lua/runtime.test.ts | 2 +- {web => lib}/space_lua/runtime.ts | 16 +- {web => lib}/space_lua/stdlib.ts | 18 +- {web => lib}/space_lua/stdlib/global_test.lua | 0 {web => lib}/space_lua/stdlib/js.ts | 0 {web => lib}/space_lua/stdlib/js_test.lua | 0 {web => lib}/space_lua/stdlib/math.ts | 0 {web => lib}/space_lua/stdlib/math_test.lua | 0 {web => lib}/space_lua/stdlib/os.ts | 12 +- {web => lib}/space_lua/stdlib/os_test.lua | 0 {web => lib}/space_lua/stdlib/printf.ts | 112 ++-- {web => lib}/space_lua/stdlib/space_lua.ts | 0 .../space_lua/stdlib/space_lua_test.lua | 0 {web => lib}/space_lua/stdlib/string.ts | 0 {web => lib}/space_lua/stdlib/string_test.lua | 0 .../space_lua/stdlib/string_test2.lua | 0 {web => lib}/space_lua/stdlib/table.ts | 0 {web => lib}/space_lua/stdlib/table_test.lua | 0 {web => lib}/space_lua/util.test.ts | 2 +- {web => lib}/space_lua/util.ts | 0 lib/spaces/fallback_space_primitives.ts | 4 + lib/spaces/filtered_space_primitives.ts | 4 + lib/spaces/http_space_primitives.ts | 31 +- lib/spaces/plug_space_primitives.ts | 3 +- lib/spaces/ro_space_primitives.ts | 6 + lib/spaces/sync.ts | 160 +++--- plug-api/syscalls/datastore.ts | 2 +- plug-api/syscalls/index.ts | 2 +- plug-api/syscalls/lua.ts | 2 +- plugs/README.md | 2 +- plugs/index/api.ts | 2 +- plugs/index/lint.ts | 3 +- plugs/index/paragraph.ts | 3 +- plugs/index/refactor.ts | 4 +- plugs/index/script.ts | 4 +- plugs/index/style.ts | 3 +- plugs/markdown/assets/preview.js | 26 +- plugs/tasks/task.ts | 7 +- scripts/generate.sh | 4 +- server/http_server.ts | 14 +- server/shell_backend.ts | 69 +-- server/space_server.ts | 2 +- web/auth.html | 358 ++++++------- web/client.ts | 502 +++++++++--------- web/client_system.ts | 13 +- web/cm_plugins/code_widget.ts | 1 + web/cm_plugins/iframe_widget.ts | 12 +- web/cm_plugins/inline_content.ts | 10 +- web/cm_plugins/list.ts | 1 + web/cm_plugins/lua_directive.ts | 13 +- web/cm_plugins/lua_widget.ts | 12 +- web/cm_plugins/table.ts | 19 +- web/cm_plugins/task.ts | 1 + web/cm_plugins/util.ts | 3 + web/commands.ts | 2 +- web/config.ts | 82 +-- web/document_editor.ts | 55 +- web/editor_state.ts | 3 +- web/editor_ui.tsx | 9 +- web/hooks/command.ts | 7 +- web/hooks/document_editor.ts | 3 +- web/hooks/event.ts | 2 +- web/hooks/plug_namespace.ts | 4 +- web/hooks/slash_command.ts | 7 +- web/hooks/syscall.ts | 3 +- web/index.html | 107 ++-- web/languages.ts | 30 +- web/manifest.json | 4 +- web/markdown.ts | 8 +- web/markdown_parser/parser.ts | 14 +- web/markdown_util.ts | 2 +- web/reducer.ts | 3 +- web/shell_stream_client.ts | 136 ++--- web/space_lua.ts | 17 +- web/space_lua_api.ts | 4 +- web/space_script.ts | 2 +- web/styles/top.scss | 13 +- web/sync_service.ts | 3 +- web/syscalls/command.ts | 2 +- web/syscalls/datastore.ts | 4 +- web/syscalls/event.ts | 2 +- web/syscalls/index.ts | 4 +- web/syscalls/lua.ts | 8 +- web/syscalls/markdown.ts | 2 +- web/type.ts | 3 +- 120 files changed, 1139 insertions(+), 1101 deletions(-) rename {web => lib}/space_lua/ast.ts (100%) rename {web => lib}/space_lua/eval.test.ts (99%) rename {web => lib}/space_lua/eval.ts (98%) rename {web => lib}/space_lua/language_core_test.lua (100%) rename {web => lib}/space_lua/lua.grammar (100%) rename {web => lib}/space_lua/lua.test.ts (86%) rename {web => lib}/space_lua/lume_test.lua (100%) rename {web => lib}/space_lua/parse-lua.js (99%) rename {web => lib}/space_lua/parse-lua.terms.js (100%) rename {web => lib}/space_lua/parse.test.ts (96%) rename {web => lib}/space_lua/parse.ts (99%) rename {web => lib}/space_lua/query_collection.test.ts (97%) rename {web => lib}/space_lua/query_collection.ts (94%) rename {web => lib}/space_lua/query_test.lua (100%) rename {web => lib}/space_lua/runtime.test.ts (98%) rename {web => lib}/space_lua/runtime.ts (100%) rename {web => lib}/space_lua/stdlib.ts (93%) rename {web => lib}/space_lua/stdlib/global_test.lua (100%) rename {web => lib}/space_lua/stdlib/js.ts (100%) rename {web => lib}/space_lua/stdlib/js_test.lua (100%) rename {web => lib}/space_lua/stdlib/math.ts (100%) rename {web => lib}/space_lua/stdlib/math_test.lua (100%) rename {web => lib}/space_lua/stdlib/os.ts (95%) rename {web => lib}/space_lua/stdlib/os_test.lua (100%) rename {web => lib}/space_lua/stdlib/printf.ts (99%) rename {web => lib}/space_lua/stdlib/space_lua.ts (100%) rename {web => lib}/space_lua/stdlib/space_lua_test.lua (100%) rename {web => lib}/space_lua/stdlib/string.ts (100%) rename {web => lib}/space_lua/stdlib/string_test.lua (100%) rename {web => lib}/space_lua/stdlib/string_test2.lua (100%) rename {web => lib}/space_lua/stdlib/table.ts (100%) rename {web => lib}/space_lua/stdlib/table_test.lua (100%) rename {web => lib}/space_lua/util.test.ts (90%) rename {web => lib}/space_lua/util.ts (100%) diff --git a/build_web.ts b/build_web.ts index e8038ebc..7dca5d3b 100644 --- a/build_web.ts +++ b/build_web.ts @@ -79,6 +79,7 @@ export async function copyAssets(dist: string) { bundleJs = patchDenoLibJS(bundleJs); await Deno.writeTextFile(`${dist}/client.js`, bundleJs); } + async function buildCopyBundleAssets() { await Deno.mkdir("dist_client_bundle", { recursive: true }); await Deno.mkdir("dist_plug_bundle", { recursive: true }); diff --git a/cmd/compile.ts b/cmd/compile.ts index f659b4e5..7d4881de 100644 --- a/cmd/compile.ts +++ b/cmd/compile.ts @@ -145,6 +145,7 @@ export async function compileManifests( ) { let building = false; dist = path.resolve(dist); + async function buildAll() { if (building) { return; diff --git a/cmd/test/runtime.test.ts b/cmd/test/runtime.test.ts index 4896b5a2..4c28506a 100644 --- a/cmd/test/runtime.test.ts +++ b/cmd/test/runtime.test.ts @@ -1,9 +1,9 @@ import { createSandbox } from "../../lib/plugos/sandboxes/deno_worker_sandbox.ts"; +import type { SysCallMapping } from "../../lib/plugos/system.ts"; import { System } from "../../lib/plugos/system.ts"; import { assertEquals } from "@std/assert"; import { compileManifest } from "../compile.ts"; import * as esbuild from "esbuild"; -import type { SysCallMapping } from "../../lib/plugos/system.ts"; import { fileURLToPath } from "node:url"; Deno.test("Run a deno sandbox", { diff --git a/deno.json b/deno.json index 602ead7d..ad021ccd 100644 --- a/deno.json +++ b/deno.json @@ -31,30 +31,23 @@ "tasks": { "clean": "rm -rf dist dist_client_bundle dist_plug_bundle", "deep-clean-mac": "rm -f deno.lock && rm -rf $HOME/Library/Caches/deno && deno task clean", - "install": "deno install -g -f -A --import-map deno.json silverbullet.ts", - "check": "find . -name '*.ts*' | xargs deno check", "lint": "deno lint", "fmt": "deno fmt", "test": "deno test -A", "bench": "deno bench", - "build": "rm -rf dist dist_client_bundle dist_plug_bundle && deno run -A build_plugs.ts && deno run -A build_web.ts && deno task compile", "plugs": "deno run -A build_plugs.ts", "server": "deno run -A --check silverbullet.ts", "generate": "./scripts/generate.sh", "docker": "./scripts/build_docker.sh", - "watch-web": "deno run -A --check build_web.ts --watch", "watch-server": "deno run -A --check --watch silverbullet.ts", "watch-plugs": "deno run -A --check build_plugs.ts -w", "watch-all": "deno task watch-web & deno task watch-plugs & deno task watch-server", - "bundle": "deno run -A build_bundle.ts", - "compile": "deno task bundle && deno compile -A -o silverbullet dist/silverbullet.js", - "server:dist:linux-x86_64": "deno task bundle && deno compile -A --target x86_64-unknown-linux-gnu -o silverbullet dist/silverbullet.js", "server:zip:linux-x86_64": "deno task server:dist:linux-x86_64 && zip silverbullet-server-linux-x86_64.zip silverbullet", "server:dist:linux-aarch64": "deno task bundle && deno compile -A --target aarch64-unknown-linux-gnu -o silverbullet dist/silverbullet.js", @@ -75,7 +68,11 @@ "cmd/test/test_space" ], "rules": { - "exclude": ["no-explicit-any", "no-slow-types", "jsx-boolean-value"] + "exclude": [ + "no-explicit-any", + "no-slow-types", + "jsx-boolean-value" + ] } }, "fmt": { @@ -90,7 +87,12 @@ ] }, "compilerOptions": { - "lib": ["dom", "dom.iterable", "dom.asynciterable", "deno.ns"], + "lib": [ + "dom", + "dom.iterable", + "dom.asynciterable", + "deno.ns" + ], "jsx": "react-jsx", "jsxImportSource": "https://esm.sh/preact@10.26.4" }, diff --git a/lib/README.md b/lib/README.md index e5e54bfc..4e240624 100644 --- a/lib/README.md +++ b/lib/README.md @@ -1 +1,2 @@ -This folder contains library functions that is generally reusable, not specific to SilverBullet. This code can be used from within plugs or the core code base. It may move out of this repo at some point. \ No newline at end of file +This folder contains library functions that is generally reusable, not specific to SilverBullet. This code can be used +from within plugs or the core code base. It may move out of this repo at some point. \ No newline at end of file diff --git a/lib/data/datastore.test.ts b/lib/data/datastore.test.ts index 25ee36fd..8b0e0d33 100644 --- a/lib/data/datastore.test.ts +++ b/lib/data/datastore.test.ts @@ -5,8 +5,8 @@ import type { KvPrimitives } from "../data/kv_primitives.ts"; import { assertEquals } from "@std/assert"; import { PrefixedKvPrimitives } from "../data/prefixed_kv_primitives.ts"; import { DataStore } from "$lib/data/datastore.ts"; -import { LuaEnv, LuaStackFrame } from "../../web/space_lua/runtime.ts"; -import { parseExpressionString } from "../../web/space_lua/parse.ts"; +import { LuaEnv, LuaStackFrame } from "../space_lua/runtime.ts"; +import { parseExpressionString } from "../space_lua/parse.ts"; async function test(db: KvPrimitives) { const datastore = new DataStore(new PrefixedKvPrimitives(db, ["ds"])); diff --git a/lib/data/datastore.ts b/lib/data/datastore.ts index f9b89e81..e26cd242 100644 --- a/lib/data/datastore.ts +++ b/lib/data/datastore.ts @@ -1,8 +1,8 @@ import { type LuaCollectionQuery, queryLua, -} from "../../web/space_lua/query_collection.ts"; -import { LuaEnv, LuaStackFrame } from "../../web/space_lua/runtime.ts"; +} from "../space_lua/query_collection.ts"; +import { LuaEnv, LuaStackFrame } from "../space_lua/runtime.ts"; import type { KV, KvKey } from "../../plug-api/types.ts"; import type { KvPrimitives, KvQueryOptions } from "./kv_primitives.ts"; diff --git a/lib/data/indexeddb_kv_primitives.ts b/lib/data/indexeddb_kv_primitives.ts index 13ca7c7b..920505ad 100644 --- a/lib/data/indexeddb_kv_primitives.ts +++ b/lib/data/indexeddb_kv_primitives.ts @@ -75,6 +75,10 @@ export class IndexedDBKvPrimitives implements KvPrimitives { } } + close() { + this.db.close(); + } + private buildKey(key: KvKey): string { for (const k of key) { if (k.includes(sep)) { @@ -87,8 +91,4 @@ export class IndexedDBKvPrimitives implements KvPrimitives { private extractKey(key: string): KvKey { return key.split(sep); } - - close() { - this.db.close(); - } } diff --git a/lib/data/kv_primitives.ts b/lib/data/kv_primitives.ts index e84d9cc6..e51a46a8 100644 --- a/lib/data/kv_primitives.ts +++ b/lib/data/kv_primitives.ts @@ -6,10 +6,15 @@ export type KvQueryOptions = { export interface KvPrimitives { batchGet(keys: KvKey[]): Promise<(any | undefined)[]>; + batchSet(entries: KV[]): Promise; + batchDelete(keys: KvKey[]): Promise; + query(options: KvQueryOptions): AsyncIterableIterator; + // Completely clear all data from this datastore clear(): Promise; + close(): void; } diff --git a/lib/data/memory_kv_primitives.ts b/lib/data/memory_kv_primitives.ts index f535d7e9..79918b24 100644 --- a/lib/data/memory_kv_primitives.ts +++ b/lib/data/memory_kv_primitives.ts @@ -28,6 +28,27 @@ export class MemoryKvPrimitives implements KvPrimitives { } } } + + static fromJSON(json: Record): MemoryKvPrimitives { + const result = new MemoryKvPrimitives(); + for (const key of Object.keys(json)) { + result.store.set(key, json[key]); + } + return result; + } + + /** + * Create a new MemoryKvPrimitives instance from a file and initialize it + */ + static async fromFile( + filePath: string, + options: { throttleMs?: number } = {}, + ): Promise { + const instance = new MemoryKvPrimitives(filePath, options); + await instance.init(); + return instance; + } + clear(): Promise { this.store.clear(); return Promise.resolve(); @@ -62,23 +83,6 @@ export class MemoryKvPrimitives implements KvPrimitives { } } - /** - * Persist the current state to disk - */ - private async persistToDisk(): Promise { - if (!this.filePath) return; - - try { - const jsonData = this.toJSON(); - await Deno.writeTextFile( - this.filePath, - JSON.stringify(jsonData, null, 2), - ); - } catch (error) { - console.error(`Failed to persist KV store to ${this.filePath}:`, error); - } - } - batchGet(keys: KvKey[]): Promise { return Promise.resolve( keys.map((key) => this.store.get(key.join(memoryKeySeparator))), @@ -125,14 +129,6 @@ export class MemoryKvPrimitives implements KvPrimitives { return result; } - static fromJSON(json: Record): MemoryKvPrimitives { - const result = new MemoryKvPrimitives(); - for (const key of Object.keys(json)) { - result.store.set(key, json[key]); - } - return result; - } - async *query(options: KvQueryOptions): AsyncIterableIterator { const prefix = options.prefix?.join(memoryKeySeparator); const sortedKeys = [...this.store.keys()].sort(); @@ -155,14 +151,19 @@ export class MemoryKvPrimitives implements KvPrimitives { } /** - * Create a new MemoryKvPrimitives instance from a file and initialize it + * Persist the current state to disk */ - static async fromFile( - filePath: string, - options: { throttleMs?: number } = {}, - ): Promise { - const instance = new MemoryKvPrimitives(filePath, options); - await instance.init(); - return instance; + private async persistToDisk(): Promise { + if (!this.filePath) return; + + try { + const jsonData = this.toJSON(); + await Deno.writeTextFile( + this.filePath, + JSON.stringify(jsonData, null, 2), + ); + } catch (error) { + console.error(`Failed to persist KV store to ${this.filePath}:`, error); + } } } diff --git a/lib/data/mq.datastore.ts b/lib/data/mq.datastore.ts index dddb0bf9..06c74a00 100644 --- a/lib/data/mq.datastore.ts +++ b/lib/data/mq.datastore.ts @@ -6,8 +6,8 @@ import type { MQSubscribeOptions, } from "../../plug-api/types.ts"; import type { DataStore } from "./datastore.ts"; -import { parseExpressionString } from "../../web/space_lua/parse.ts"; -import { LuaEnv } from "../../web/space_lua/runtime.ts"; +import { parseExpressionString } from "../space_lua/parse.ts"; +import { LuaEnv } from "../space_lua/runtime.ts"; export type ProcessingMessage = MQMessage & { ts: number; @@ -20,15 +20,14 @@ const dlqPrefix = ["mq", "dlq"]; export class DataStoreMQ { // queue -> set of run() functions localSubscriptions = new Map void>>(); + // Internal sequencer for messages, only really necessary when batch sending tons of messages within a millisecond + seq = 0; constructor( private ds: DataStore, ) { } - // Internal sequencer for messages, only really necessary when batch sending tons of messages within a millisecond - seq = 0; - async batchSend(queue: string, bodies: any[]): Promise { if (bodies.length === 0) { return; diff --git a/lib/data/prefixed_kv_primitives.ts b/lib/data/prefixed_kv_primitives.ts index 98bfd08c..1c125458 100644 --- a/lib/data/prefixed_kv_primitives.ts +++ b/lib/data/prefixed_kv_primitives.ts @@ -35,6 +35,7 @@ export class PrefixedKvPrimitives implements KvPrimitives { yield { key: this.stripPrefix(result.key), value: result.value }; } } + close(): void { this.wrapped.close(); } diff --git a/lib/memory_cache.test.ts b/lib/memory_cache.test.ts index a43797f4..d1fb31f0 100644 --- a/lib/memory_cache.test.ts +++ b/lib/memory_cache.test.ts @@ -4,11 +4,13 @@ import { assertEquals } from "@std/assert"; Deno.test("Memory cache", async () => { let calls = 0; + async function expensiveFunction(key: string) { calls++; await sleep(1); return key; } + assertEquals("key", await ttlCache("key", expensiveFunction, 0.01)); assertEquals(1, calls); assertEquals("key", await ttlCache("key", expensiveFunction, 0.01)); diff --git a/lib/plugos/eventhook.ts b/lib/plugos/eventhook.ts index 1f286d16..8830693b 100644 --- a/lib/plugos/eventhook.ts +++ b/lib/plugos/eventhook.ts @@ -3,5 +3,6 @@ import type { Hook } from "./types.ts"; export interface EventHookI extends Hook { dispatchEvent(eventName: string, ...args: unknown[]): Promise; + listEvents(): string[]; } diff --git a/lib/plugos/hooks/mq.ts b/lib/plugos/hooks/mq.ts index 6f34dc0d..90f7b942 100644 --- a/lib/plugos/hooks/mq.ts +++ b/lib/plugos/hooks/mq.ts @@ -7,6 +7,9 @@ import type { DataStoreMQ } from "$lib/data/mq.datastore.ts"; export class MQHook implements Hook { subscriptions: (() => void)[] = []; + throttledReloadQueues = throttle(() => { + this.reloadQueues(); + }, 1000); constructor(private system: System, readonly mq: DataStoreMQ) { } @@ -30,10 +33,6 @@ export class MQHook implements Hook { this.subscriptions = []; } - throttledReloadQueues = throttle(() => { - this.reloadQueues(); - }, 1000); - reloadQueues() { this.stop(); for (const plug of this.system.loadedPlugs.values()) { diff --git a/lib/plugos/sandboxes/no_sandbox.ts b/lib/plugos/sandboxes/no_sandbox.ts index 8cccd119..7229b57a 100644 --- a/lib/plugos/sandboxes/no_sandbox.ts +++ b/lib/plugos/sandboxes/no_sandbox.ts @@ -1,8 +1,7 @@ import type { Plug } from "../plug.ts"; -import type { Sandbox } from "./sandbox.ts"; +import type { Sandbox, SandboxFactory } from "./sandbox.ts"; import type { Manifest } from "../types.ts"; import type { System } from "../system.ts"; -import type { SandboxFactory } from "./sandbox.ts"; import { PromiseQueue } from "../../async.ts"; /** diff --git a/lib/plugos/sandboxes/sandbox.ts b/lib/plugos/sandboxes/sandbox.ts index 492fc890..0f0d36a9 100644 --- a/lib/plugos/sandboxes/sandbox.ts +++ b/lib/plugos/sandboxes/sandbox.ts @@ -5,7 +5,10 @@ export type SandboxFactory = (plug: Plug) => Sandbox; export interface Sandbox { manifest?: Manifest; + init(): Promise; + invoke(name: string, args: any[]): Promise; + stop(): void; } diff --git a/lib/plugos/sandboxes/worker_sandbox.ts b/lib/plugos/sandboxes/worker_sandbox.ts index ebac3b91..906adf24 100644 --- a/lib/plugos/sandboxes/worker_sandbox.ts +++ b/lib/plugos/sandboxes/worker_sandbox.ts @@ -9,6 +9,7 @@ import type { Sandbox } from "./sandbox.ts"; * Effectively this wraps a web worker, the reason to have this split from Plugs is to allow plugs to manage multiple sandboxes, e.g. for performance in the future */ export class WorkerSandbox implements Sandbox { + public manifest?: Manifest; private worker?: Worker; private reqId = 0; private outstandingInvocations = new Map< @@ -16,9 +17,6 @@ export class WorkerSandbox implements Sandbox { { resolve: (result: any) => void; reject: (e: any) => void } >(); - // public ready: Promise; - public manifest?: Manifest; - constructor( readonly plug: Plug, public workerUrl: URL, diff --git a/lib/plugos/system.ts b/lib/plugos/system.ts index 5c214c22..f2b7d5ae 100644 --- a/lib/plugos/system.ts +++ b/lib/plugos/system.ts @@ -35,8 +35,8 @@ export type SystemOptions = { }; export class System extends EventEmitter> { - protected plugs = new Map>(); registeredSyscalls = new Map(); + protected plugs = new Map>(); protected enabledHooks = new Set>(); /** diff --git a/lib/plugos/types.ts b/lib/plugos/types.ts index c2fa355d..9417c9c0 100644 --- a/lib/plugos/types.ts +++ b/lib/plugos/types.ts @@ -55,5 +55,6 @@ export type FunctionDef = { export interface Hook { validateManifest(manifest: Manifest): string[]; + apply(system: System): void; } diff --git a/lib/plugos/worker_runtime.ts b/lib/plugos/worker_runtime.ts index 028d1e90..796d229b 100644 --- a/lib/plugos/worker_runtime.ts +++ b/lib/plugos/worker_runtime.ts @@ -177,6 +177,7 @@ export async function sandboxFetch( // @ts-ignore: monkey patching fetch globalThis.nativeFetch = globalThis.fetch; + // Monkey patch fetch() export function monkeyPatchFetch() { // @ts-ignore: monkey patching fetch diff --git a/web/space_lua/ast.ts b/lib/space_lua/ast.ts similarity index 100% rename from web/space_lua/ast.ts rename to lib/space_lua/ast.ts diff --git a/web/space_lua/eval.test.ts b/lib/space_lua/eval.test.ts similarity index 99% rename from web/space_lua/eval.test.ts rename to lib/space_lua/eval.test.ts index 087c6df2..840b7c19 100644 --- a/web/space_lua/eval.test.ts +++ b/lib/space_lua/eval.test.ts @@ -10,7 +10,7 @@ import { import { parse } from "./parse.ts"; import type { LuaBlock, LuaFunctionCallStatement } from "./ast.ts"; import { evalExpression, evalStatement } from "./eval.ts"; -import { luaBuildStandardEnv } from "../space_lua/stdlib.ts"; +import { luaBuildStandardEnv } from "./stdlib.ts"; const sf = LuaStackFrame.lostFrame; diff --git a/web/space_lua/eval.ts b/lib/space_lua/eval.ts similarity index 98% rename from web/space_lua/eval.ts rename to lib/space_lua/eval.ts index eb9a686d..03ec064b 100644 --- a/web/space_lua/eval.ts +++ b/lib/space_lua/eval.ts @@ -1,42 +1,35 @@ -import type { - ASTCtx, - LuaExpression, - LuaLValue, - LuaStatement, -} from "../space_lua/ast.ts"; -import { evalPromiseValues } from "../space_lua/util.ts"; -import { - luaCall, - luaEquals, - luaIndexValue, - luaSet, - type LuaStackFrame, -} from "../space_lua/runtime.ts"; +import type { ASTCtx, LuaExpression, LuaLValue, LuaStatement } from "./ast.ts"; +import { evalPromiseValues } from "./util.ts"; import { type ILuaFunction, type ILuaGettable, type ILuaSettable, + jsToLuaValue, LuaBreak, + luaCall, LuaEnv, + luaEquals, LuaFunction, luaGet, + luaIndexValue, luaLen, type LuaLValueContainer, LuaMultiRes, LuaReturn, LuaRuntimeError, + luaSet, + type LuaStackFrame, LuaTable, luaToString, luaTruthy, type LuaValue, + luaValueToJS, singleResult, } from "./runtime.ts"; import { ArrayQueryCollection, type LuaCollectionQuery, -} from "../space_lua/query_collection.ts"; -import { luaValueToJS } from "../space_lua/runtime.ts"; -import { jsToLuaValue } from "../space_lua/runtime.ts"; +} from "./query_collection.ts"; async function handleTableFieldSync( table: LuaTable, diff --git a/web/space_lua/language_core_test.lua b/lib/space_lua/language_core_test.lua similarity index 100% rename from web/space_lua/language_core_test.lua rename to lib/space_lua/language_core_test.lua diff --git a/web/space_lua/lua.grammar b/lib/space_lua/lua.grammar similarity index 100% rename from web/space_lua/lua.grammar rename to lib/space_lua/lua.grammar diff --git a/web/space_lua/lua.test.ts b/lib/space_lua/lua.test.ts similarity index 86% rename from web/space_lua/lua.test.ts rename to lib/space_lua/lua.test.ts index 786f6c54..2f93fef9 100644 --- a/web/space_lua/lua.test.ts +++ b/lib/space_lua/lua.test.ts @@ -1,11 +1,7 @@ -import { parse } from "../space_lua/parse.ts"; -import { luaBuildStandardEnv } from "../space_lua/stdlib.ts"; -import { - LuaEnv, - LuaRuntimeError, - LuaStackFrame, -} from "../space_lua/runtime.ts"; -import { evalStatement } from "../space_lua/eval.ts"; +import { parse } from "./parse.ts"; +import { luaBuildStandardEnv } from "./stdlib.ts"; +import { LuaEnv, LuaRuntimeError, LuaStackFrame } from "./runtime.ts"; +import { evalStatement } from "./eval.ts"; import { assert } from "@std/assert/assert"; import { fileURLToPath } from "node:url"; diff --git a/web/space_lua/lume_test.lua b/lib/space_lua/lume_test.lua similarity index 100% rename from web/space_lua/lume_test.lua rename to lib/space_lua/lume_test.lua diff --git a/web/space_lua/parse-lua.js b/lib/space_lua/parse-lua.js similarity index 99% rename from web/space_lua/parse-lua.js rename to lib/space_lua/parse-lua.js index 9e7bdbdf..c46e7e93 100644 --- a/web/space_lua/parse-lua.js +++ b/lib/space_lua/parse-lua.js @@ -20,4 +20,3 @@ export const parser = LRParser.deserialize({ specialized: [{term: 119, get: (value) => spec_identifier[value] || -1}], tokenPrec: 3791 }) - diff --git a/web/space_lua/parse-lua.terms.js b/lib/space_lua/parse-lua.terms.js similarity index 100% rename from web/space_lua/parse-lua.terms.js rename to lib/space_lua/parse-lua.terms.js diff --git a/web/space_lua/parse.test.ts b/lib/space_lua/parse.test.ts similarity index 96% rename from web/space_lua/parse.test.ts rename to lib/space_lua/parse.test.ts index feb820e5..ab2b8e05 100644 --- a/web/space_lua/parse.test.ts +++ b/lib/space_lua/parse.test.ts @@ -1,9 +1,5 @@ -import { - parse, - parseExpressionString, - stripLuaComments, -} from "../space_lua/parse.ts"; -import type { LuaNumberLiteral } from "../space_lua/ast.ts"; +import { parse, parseExpressionString, stripLuaComments } from "./parse.ts"; +import type { LuaNumberLiteral } from "./ast.ts"; import { assertEquals } from "@std/assert/equals"; Deno.test("Test Lua parser", () => { diff --git a/web/space_lua/parse.ts b/lib/space_lua/parse.ts similarity index 99% rename from web/space_lua/parse.ts rename to lib/space_lua/parse.ts index 1860e08c..ad46881c 100644 --- a/web/space_lua/parse.ts +++ b/lib/space_lua/parse.ts @@ -1,10 +1,10 @@ -import { lezerToParseTree } from "../markdown_parser/parse_tree.ts"; +import { lezerToParseTree } from "../../web/markdown_parser/parse_tree.ts"; import { cleanTree, type ParseTree, } from "@silverbulletmd/silverbullet/lib/tree"; import { parser } from "./parse-lua.js"; -import { styleTags } from "@lezer/highlight"; +import { styleTags, tags as t } from "@lezer/highlight"; import { indentNodeProp, LRLanguage } from "@codemirror/language"; import type { ASTCtx, @@ -22,7 +22,6 @@ import type { LuaStatement, LuaTableField, } from "./ast.ts"; -import { tags as t } from "@lezer/highlight"; const luaStyleTags = styleTags({ Name: t.variableName, diff --git a/web/space_lua/query_collection.test.ts b/lib/space_lua/query_collection.test.ts similarity index 97% rename from web/space_lua/query_collection.test.ts rename to lib/space_lua/query_collection.test.ts index 28a7e69c..098b2db6 100644 --- a/web/space_lua/query_collection.test.ts +++ b/lib/space_lua/query_collection.test.ts @@ -1,10 +1,6 @@ -import { parseExpressionString } from "../space_lua/parse.ts"; +import { parseExpressionString } from "./parse.ts"; import { ArrayQueryCollection } from "./query_collection.ts"; -import { - LuaEnv, - LuaNativeJSFunction, - LuaStackFrame, -} from "../space_lua/runtime.ts"; +import { LuaEnv, LuaNativeJSFunction, LuaStackFrame } from "./runtime.ts"; import { assert, assertEquals } from "@std/assert"; Deno.test("ArrayQueryCollection", async () => { diff --git a/web/space_lua/query_collection.ts b/lib/space_lua/query_collection.ts similarity index 94% rename from web/space_lua/query_collection.ts rename to lib/space_lua/query_collection.ts index a4b091dc..982b0269 100644 --- a/web/space_lua/query_collection.ts +++ b/lib/space_lua/query_collection.ts @@ -1,12 +1,7 @@ -import type { LuaExpression } from "../space_lua/ast.ts"; -import { - LuaEnv, - luaGet, - luaKeys, - LuaStackFrame, -} from "../space_lua/runtime.ts"; -import { evalExpression } from "../space_lua/eval.ts"; -import { asyncQuickSort } from "../space_lua/util.ts"; +import type { LuaExpression } from "./ast.ts"; +import { LuaEnv, luaGet, luaKeys, LuaStackFrame } from "./runtime.ts"; +import { evalExpression } from "./eval.ts"; +import { asyncQuickSort } from "./util.ts"; import type { DataStore } from "$lib/data/datastore.ts"; import type { KvKey } from "@silverbulletmd/silverbullet/types"; import type { KvPrimitives } from "$lib/data/kv_primitives.ts"; @@ -68,7 +63,8 @@ export interface LuaQueryCollection { * Implements a query collection for a regular JavaScript array */ export class ArrayQueryCollection implements LuaQueryCollection { - constructor(private readonly array: T[]) {} + constructor(private readonly array: T[]) { + } async query( query: LuaCollectionQuery, @@ -212,7 +208,8 @@ export class DataStoreQueryCollection implements LuaQueryCollection { constructor( private readonly dataStore: DataStore, readonly prefix: string[], - ) {} + ) { + } query( query: LuaCollectionQuery, diff --git a/web/space_lua/query_test.lua b/lib/space_lua/query_test.lua similarity index 100% rename from web/space_lua/query_test.lua rename to lib/space_lua/query_test.lua diff --git a/web/space_lua/runtime.test.ts b/lib/space_lua/runtime.test.ts similarity index 98% rename from web/space_lua/runtime.test.ts rename to lib/space_lua/runtime.test.ts index cf58c3f2..9a857301 100644 --- a/web/space_lua/runtime.test.ts +++ b/lib/space_lua/runtime.test.ts @@ -5,7 +5,7 @@ import { LuaMultiRes, LuaStackFrame, luaToString, -} from "../space_lua/runtime.ts"; +} from "./runtime.ts"; Deno.test("Test Lua Rutime", async () => { // Test LuaMultires diff --git a/web/space_lua/runtime.ts b/lib/space_lua/runtime.ts similarity index 100% rename from web/space_lua/runtime.ts rename to lib/space_lua/runtime.ts index 8d4dd2a8..8f5821d9 100644 --- a/web/space_lua/runtime.ts +++ b/lib/space_lua/runtime.ts @@ -18,6 +18,7 @@ export type JSValue = any; export interface ILuaFunction { call(sf: LuaStackFrame, ...args: LuaValue[]): Promise | LuaValue; + asString(): string; } @@ -94,6 +95,8 @@ export class LuaEnv implements ILuaSettable, ILuaGettable { } export class LuaStackFrame { + static lostFrame = new LuaStackFrame(new LuaEnv(), null); + constructor( readonly threadLocal: LuaEnv, readonly astCtx: ASTCtx | null, @@ -101,12 +104,6 @@ export class LuaStackFrame { ) { } - withCtx(ctx: ASTCtx): LuaStackFrame { - return new LuaStackFrame(this.threadLocal, ctx, this); - } - - static lostFrame = new LuaStackFrame(new LuaEnv(), null); - static createWithGlobalEnv( globalEnv: LuaEnv, ctx: ASTCtx | null = null, @@ -115,6 +112,10 @@ export class LuaStackFrame { env.setLocal("_GLOBAL", globalEnv); return new LuaStackFrame(env, ctx); } + + withCtx(ctx: ASTCtx): LuaStackFrame { + return new LuaStackFrame(this.threadLocal, ctx, this); + } } export class LuaMultiRes { @@ -279,6 +280,7 @@ export class LuaBuiltinFunction implements ILuaFunction { export class LuaTable implements ILuaSettable, ILuaGettable { // To optimize the table implementation we use a combination of different data structures + public metatable: LuaTable | null; // When tables are used as maps, the common case is that they are string keys, so we use a simple object for that private stringKeys: Record; // Other keys we can support using a Map as a fallback @@ -286,8 +288,6 @@ export class LuaTable implements ILuaSettable, ILuaGettable { // When tables are used as arrays, we use a native JavaScript array for that private arrayPart: any[]; - public metatable: LuaTable | null; - constructor(init?: any[] | Record) { // For efficiency and performance reasons we pre-allocate these (modern JS engines are very good at optimizing this) this.arrayPart = Array.isArray(init) ? init : []; diff --git a/web/space_lua/stdlib.ts b/lib/space_lua/stdlib.ts similarity index 93% rename from web/space_lua/stdlib.ts rename to lib/space_lua/stdlib.ts index 780a3330..be569e55 100644 --- a/web/space_lua/stdlib.ts +++ b/lib/space_lua/stdlib.ts @@ -11,15 +11,15 @@ import { luaToString, luaTypeOf, type LuaValue, -} from "../space_lua/runtime.ts"; -import { stringApi } from "../space_lua/stdlib/string.ts"; -import { tableApi } from "../space_lua/stdlib/table.ts"; -import { osApi } from "../space_lua/stdlib/os.ts"; -import { jsApi } from "../space_lua/stdlib/js.ts"; -import { spaceluaApi } from "../space_lua/stdlib/space_lua.ts"; -import { mathApi } from "../space_lua/stdlib/math.ts"; -import { parse } from "../space_lua/parse.ts"; -import { evalStatement } from "../space_lua/eval.ts"; +} from "./runtime.ts"; +import { stringApi } from "./stdlib/string.ts"; +import { tableApi } from "./stdlib/table.ts"; +import { osApi } from "./stdlib/os.ts"; +import { jsApi } from "./stdlib/js.ts"; +import { spaceluaApi } from "./stdlib/space_lua.ts"; +import { mathApi } from "./stdlib/math.ts"; +import { parse } from "./parse.ts"; +import { evalStatement } from "./eval.ts"; const printFunction = new LuaBuiltinFunction(async (_sf, ...args) => { console.log( diff --git a/web/space_lua/stdlib/global_test.lua b/lib/space_lua/stdlib/global_test.lua similarity index 100% rename from web/space_lua/stdlib/global_test.lua rename to lib/space_lua/stdlib/global_test.lua diff --git a/web/space_lua/stdlib/js.ts b/lib/space_lua/stdlib/js.ts similarity index 100% rename from web/space_lua/stdlib/js.ts rename to lib/space_lua/stdlib/js.ts diff --git a/web/space_lua/stdlib/js_test.lua b/lib/space_lua/stdlib/js_test.lua similarity index 100% rename from web/space_lua/stdlib/js_test.lua rename to lib/space_lua/stdlib/js_test.lua diff --git a/web/space_lua/stdlib/math.ts b/lib/space_lua/stdlib/math.ts similarity index 100% rename from web/space_lua/stdlib/math.ts rename to lib/space_lua/stdlib/math.ts diff --git a/web/space_lua/stdlib/math_test.lua b/lib/space_lua/stdlib/math_test.lua similarity index 100% rename from web/space_lua/stdlib/math_test.lua rename to lib/space_lua/stdlib/math_test.lua diff --git a/web/space_lua/stdlib/os.ts b/lib/space_lua/stdlib/os.ts similarity index 95% rename from web/space_lua/stdlib/os.ts rename to lib/space_lua/stdlib/os.ts index e17c7a02..0aadd70c 100644 --- a/web/space_lua/stdlib/os.ts +++ b/lib/space_lua/stdlib/os.ts @@ -92,13 +92,13 @@ export const osApi = new LuaTable({ if (format === "*t") { /* - To produce a date table, we use the format string "*t". For instance, the following code + To produce a date table, we use the format string "*t". For instance, the following code - temp = os.date("*t", 906000490) -produces the table - {year = 1998, month = 9, day = 16, yday = 259, wday = 4, - hour = 23, min = 48, sec = 10, isdst = false} - */ + temp = os.date("*t", 906000490) + produces the table + {year = 1998, month = 9, day = 16, yday = 259, wday = 4, + hour = 23, min = 48, sec = 10, isdst = false} + */ return new LuaTable({ year: date.getFullYear(), month: date.getMonth() + 1, diff --git a/web/space_lua/stdlib/os_test.lua b/lib/space_lua/stdlib/os_test.lua similarity index 100% rename from web/space_lua/stdlib/os_test.lua rename to lib/space_lua/stdlib/os_test.lua diff --git a/web/space_lua/stdlib/printf.ts b/lib/space_lua/stdlib/printf.ts similarity index 99% rename from web/space_lua/stdlib/printf.ts rename to lib/space_lua/stdlib/printf.ts index d63fc8a2..d524c2dc 100644 --- a/web/space_lua/stdlib/printf.ts +++ b/lib/space_lua/stdlib/printf.ts @@ -74,6 +74,7 @@ interface TokenInfo { setArg?: (token: TokenInfo) => void; setMaxWidth?: (token: TokenInfo) => void; extend?: string[]; + [key: string]: any; // Allow dynamic property access } @@ -88,6 +89,7 @@ interface SpecifierInfo { isDouble?: boolean; doubleNotation?: string; isObject?: boolean; + [key: string]: any; // Allow dynamic property access } @@ -181,29 +183,6 @@ class Formatter { // The solution replace the sub-regexp (\*|\d+)?(\.)?(\*|\d+)? with the sub-regexp `(\*|\d+)?(?:(\.)(\*|\d+)?)?`, see the figure in [#32](https://github.com/adaltas/node-printf/pull/32) // There are also performance improvement, see in [#31](https://github.com/adaltas/node-printf/issues/31#issuecomment-776731490) - private _parseDelim( - mapping?: string, - intmapping?: string, - flags?: string, - minWidth?: string, - period?: string, - precision?: string, - specifier?: string, - ): TokenInfo { - if (mapping) { - this._mapped = true; - } - return { - mapping, - intmapping, - flags: flags || "", - _minWidth: minWidth, - period, - _precision: precision, - specifier: specifier || "", - }; - } - format(...args: any[]): string { if (this._mapped && typeof args[0] !== "object") { throw new Error("format requires a mapping"); @@ -543,38 +522,6 @@ class Formatter { } } - // Helper method to limit object depth for formatObject - private limitObjectDepth(obj: any, maxDepth: number, currentDepth = 0): any { - if (currentDepth >= maxDepth) { - if (Array.isArray(obj)) { - return "[Array]"; - } else if (typeof obj === "object" && obj !== null) { - return "[Object]"; - } - return obj; - } - - if (Array.isArray(obj)) { - return obj.map((item) => - this.limitObjectDepth(item, maxDepth, currentDepth + 1) - ); - } else if (typeof obj === "object" && obj !== null) { - const result: Record = {}; - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - result[key] = this.limitObjectDepth( - obj[key], - maxDepth, - currentDepth + 1, - ); - } - } - return result; - } - - return obj; - } - zeroPad(token: TokenInfo, length?: number): void { length = (arguments.length === 2) ? length : token.precision; let negative = false; @@ -629,6 +576,61 @@ class Formatter { ? token.arg + this._spaces10.substring(0, pad) : this._spaces10.substring(0, pad) + token.arg; } + + private _parseDelim( + mapping?: string, + intmapping?: string, + flags?: string, + minWidth?: string, + period?: string, + precision?: string, + specifier?: string, + ): TokenInfo { + if (mapping) { + this._mapped = true; + } + return { + mapping, + intmapping, + flags: flags || "", + _minWidth: minWidth, + period, + _precision: precision, + specifier: specifier || "", + }; + } + + // Helper method to limit object depth for formatObject + private limitObjectDepth(obj: any, maxDepth: number, currentDepth = 0): any { + if (currentDepth >= maxDepth) { + if (Array.isArray(obj)) { + return "[Array]"; + } else if (typeof obj === "object" && obj !== null) { + return "[Object]"; + } + return obj; + } + + if (Array.isArray(obj)) { + return obj.map((item) => + this.limitObjectDepth(item, maxDepth, currentDepth + 1) + ); + } else if (typeof obj === "object" && obj !== null) { + const result: Record = {}; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + result[key] = this.limitObjectDepth( + obj[key], + maxDepth, + currentDepth + 1, + ); + } + } + return result; + } + + return obj; + } } /** diff --git a/web/space_lua/stdlib/space_lua.ts b/lib/space_lua/stdlib/space_lua.ts similarity index 100% rename from web/space_lua/stdlib/space_lua.ts rename to lib/space_lua/stdlib/space_lua.ts diff --git a/web/space_lua/stdlib/space_lua_test.lua b/lib/space_lua/stdlib/space_lua_test.lua similarity index 100% rename from web/space_lua/stdlib/space_lua_test.lua rename to lib/space_lua/stdlib/space_lua_test.lua diff --git a/web/space_lua/stdlib/string.ts b/lib/space_lua/stdlib/string.ts similarity index 100% rename from web/space_lua/stdlib/string.ts rename to lib/space_lua/stdlib/string.ts diff --git a/web/space_lua/stdlib/string_test.lua b/lib/space_lua/stdlib/string_test.lua similarity index 100% rename from web/space_lua/stdlib/string_test.lua rename to lib/space_lua/stdlib/string_test.lua diff --git a/web/space_lua/stdlib/string_test2.lua b/lib/space_lua/stdlib/string_test2.lua similarity index 100% rename from web/space_lua/stdlib/string_test2.lua rename to lib/space_lua/stdlib/string_test2.lua diff --git a/web/space_lua/stdlib/table.ts b/lib/space_lua/stdlib/table.ts similarity index 100% rename from web/space_lua/stdlib/table.ts rename to lib/space_lua/stdlib/table.ts diff --git a/web/space_lua/stdlib/table_test.lua b/lib/space_lua/stdlib/table_test.lua similarity index 100% rename from web/space_lua/stdlib/table_test.lua rename to lib/space_lua/stdlib/table_test.lua diff --git a/web/space_lua/util.test.ts b/lib/space_lua/util.test.ts similarity index 90% rename from web/space_lua/util.test.ts rename to lib/space_lua/util.test.ts index d03d8651..a210e0ff 100644 --- a/web/space_lua/util.test.ts +++ b/lib/space_lua/util.test.ts @@ -1,4 +1,4 @@ -import { evalPromiseValues } from "../space_lua/util.ts"; +import { evalPromiseValues } from "./util.ts"; import { assertEquals } from "@std/assert/equals"; import { assert } from "@std/assert"; diff --git a/web/space_lua/util.ts b/lib/space_lua/util.ts similarity index 100% rename from web/space_lua/util.ts rename to lib/space_lua/util.ts diff --git a/lib/spaces/fallback_space_primitives.ts b/lib/spaces/fallback_space_primitives.ts index e5e43931..627cd4c2 100644 --- a/lib/spaces/fallback_space_primitives.ts +++ b/lib/spaces/fallback_space_primitives.ts @@ -14,6 +14,7 @@ export class FallbackSpacePrimitives implements SpacePrimitives { private fallback: SpacePrimitives, ) { } + fetchFileList(): Promise { return this.primary.fetchFileList(); } @@ -43,6 +44,7 @@ export class FallbackSpacePrimitives implements SpacePrimitives { } } } + async getFileMeta(name: string): Promise { try { return await this.primary.getFileMeta(name); @@ -68,6 +70,7 @@ export class FallbackSpacePrimitives implements SpacePrimitives { } } } + writeFile( name: string, data: Uint8Array, @@ -76,6 +79,7 @@ export class FallbackSpacePrimitives implements SpacePrimitives { ): Promise { return this.primary.writeFile(name, data, selfUpdate, meta); } + deleteFile(name: string): Promise { return this.primary.deleteFile(name); } diff --git a/lib/spaces/filtered_space_primitives.ts b/lib/spaces/filtered_space_primitives.ts index ab9ef028..a57c25c5 100644 --- a/lib/spaces/filtered_space_primitives.ts +++ b/lib/spaces/filtered_space_primitives.ts @@ -15,12 +15,15 @@ export class FilteredSpacePrimitives implements SpacePrimitives { } return (await this.wrapped.fetchFileList()).filter(this.filterFn); } + readFile(name: string): Promise<{ data: Uint8Array; meta: FileMeta }> { return this.wrapped.readFile(name); } + getFileMeta(name: string): Promise { return this.wrapped.getFileMeta(name); } + writeFile( name: string, data: Uint8Array, @@ -29,6 +32,7 @@ export class FilteredSpacePrimitives implements SpacePrimitives { ): Promise { return this.wrapped.writeFile(name, data, selfUpdate, meta); } + deleteFile(name: string): Promise { return this.wrapped.deleteFile(name); } diff --git a/lib/spaces/http_space_primitives.ts b/lib/spaces/http_space_primitives.ts index 233542a3..59313d4c 100644 --- a/lib/spaces/http_space_primitives.ts +++ b/lib/spaces/http_space_primitives.ts @@ -221,21 +221,6 @@ export class HttpSpacePrimitives implements SpacePrimitives { return this.responseToMeta(name, res); } - private responseToMeta(name: string, res: Response): FileMeta { - return { - name, - // The server may set a custom X-Content-Length header in case a GET request was sent with X-Get-Meta, in which case the body may be omitted - size: res.headers.has("X-Content-Length") - ? +res.headers.get("X-Content-Length")! - : +res.headers.get("Content-Length")!, - contentType: res.headers.get("Content-type")!, - created: +(res.headers.get("X-Created") || "0"), - lastModified: +(res.headers.get("X-Last-Modified") || "0"), - perm: (res.headers.get("X-Permission") as "rw" | "ro") || "ro", - }; - } - - // Used to check if the server is reachable and the user is authenticated // If not: throws an error or invokes a redirect async ping() { const response = await this.authenticatedFetch(`${this.url}/.ping`, { @@ -249,6 +234,8 @@ export class HttpSpacePrimitives implements SpacePrimitives { await response.text(); } + // Used to check if the server is reachable and the user is authenticated + /** * Create an authenticated WebSocket connection * @param path The path to connect to @@ -277,4 +264,18 @@ export class HttpSpacePrimitives implements SpacePrimitives { // Create and return the WebSocket return new WebSocket(wsUrl); } + + private responseToMeta(name: string, res: Response): FileMeta { + return { + name, + // The server may set a custom X-Content-Length header in case a GET request was sent with X-Get-Meta, in which case the body may be omitted + size: res.headers.has("X-Content-Length") + ? +res.headers.get("X-Content-Length")! + : +res.headers.get("Content-Length")!, + contentType: res.headers.get("Content-type")!, + created: +(res.headers.get("X-Created") || "0"), + lastModified: +(res.headers.get("X-Last-Modified") || "0"), + perm: (res.headers.get("X-Permission") as "rw" | "ro") || "ro", + }; + } } diff --git a/lib/spaces/plug_space_primitives.ts b/lib/spaces/plug_space_primitives.ts index 1487de7c..1935297d 100644 --- a/lib/spaces/plug_space_primitives.ts +++ b/lib/spaces/plug_space_primitives.ts @@ -8,7 +8,8 @@ export class PlugSpacePrimitives implements SpacePrimitives { private wrapped: SpacePrimitives, private hook: PlugNamespaceHook, private env?: string, - ) {} + ) { + } // Used e.g. by the sync engine to see if it should sync a certain path (likely not the case when we have a plug space override) public isLikelyHandled(path: string): boolean { diff --git a/lib/spaces/ro_space_primitives.ts b/lib/spaces/ro_space_primitives.ts index 79ee4c51..bffed68c 100644 --- a/lib/spaces/ro_space_primitives.ts +++ b/lib/spaces/ro_space_primitives.ts @@ -3,15 +3,18 @@ import type { SpacePrimitives } from "./space_primitives.ts"; export class ReadOnlySpacePrimitives implements SpacePrimitives { wrapped: SpacePrimitives; + constructor(wrapped: SpacePrimitives) { this.wrapped = wrapped; } + async fetchFileList(): Promise { return (await this.wrapped.fetchFileList()).map((f: FileMeta) => ({ ...f, perm: "ro", })); } + async readFile(name: string): Promise<{ meta: FileMeta; data: Uint8Array }> { const { meta, data } = await this.wrapped.readFile(name); return { @@ -22,6 +25,7 @@ export class ReadOnlySpacePrimitives implements SpacePrimitives { data, }; } + async getFileMeta(name: string): Promise { const meta = await this.wrapped.getFileMeta(name); return { @@ -29,9 +33,11 @@ export class ReadOnlySpacePrimitives implements SpacePrimitives { perm: "ro", }; } + writeFile(): Promise { throw new Error("Read only space, not allowed to write"); } + deleteFile(): Promise { throw new Error("Read only space, not allowed to delete"); } diff --git a/lib/spaces/sync.ts b/lib/spaces/sync.ts index fc43a253..a36e8eb4 100644 --- a/lib/spaces/sync.ts +++ b/lib/spaces/sync.ts @@ -42,6 +42,86 @@ export class SpaceSync extends EventEmitter { super(); } + // Strategy: Primary wins + public static async primaryConflictResolver( + name: string, + snapshot: Map, + primary: SpacePrimitives, + secondary: SpacePrimitives, + ): Promise { + console.log("[sync]", "Starting conflict resolution for", name); + const filePieces = name.split("."); + const fileNameBase = filePieces.slice(0, -1).join("."); + const fileNameExt = filePieces[filePieces.length - 1]; + const pageData1 = await primary.readFile(name); + const pageData2 = await secondary.readFile(name); + + let byteWiseMatch = true; + const arrayBuffer1 = pageData1.data; + const arrayBuffer2 = pageData2.data; + if (arrayBuffer1.byteLength !== arrayBuffer2.byteLength) { + byteWiseMatch = false; + } + if (byteWiseMatch) { + // Byte-wise comparison + for (let i = 0; i < arrayBuffer1.byteLength; i++) { + if (arrayBuffer1[i] !== arrayBuffer2[i]) { + byteWiseMatch = false; + break; + } + } + // Byte wise they're still the same, so no confict + if (byteWiseMatch) { + console.log("[sync]", "Files are the same, no conflict"); + + snapshot.set(name, [ + pageData1.meta.lastModified, + pageData2.meta.lastModified, + ]); + return 0; + } + } + + let operations = 0; + const revisionFileName = filePieces.length === 1 + ? `${name}.conflicted.${pageData2.meta.lastModified}` + : `${fileNameBase}.conflicted.${pageData2.meta.lastModified}.${fileNameExt}`; + console.log( + "[sync]", + "Going to create conflicting copy", + revisionFileName, + ); + + // Copy secondary to conflict copy + const localConflictMeta = await primary.writeFile( + revisionFileName, + pageData2.data, + ); + operations++; + const remoteConflictMeta = await secondary.writeFile( + revisionFileName, + pageData2.data, + ); + operations++; + + // Write replacement on top + const writeMeta = await secondary.writeFile( + name, + pageData1.data, + true, + ); + operations++; + + // Updating snapshot + snapshot.set(revisionFileName, [ + localConflictMeta.lastModified, + remoteConflictMeta.lastModified, + ]); + + snapshot.set(name, [pageData1.meta.lastModified, writeMeta.lastModified]); + return operations; + } + async syncFiles( snapshot: Map, isSyncCandidate = this.options.isSyncCandidate, @@ -289,86 +369,6 @@ export class SpaceSync extends EventEmitter { return operations; } - // Strategy: Primary wins - public static async primaryConflictResolver( - name: string, - snapshot: Map, - primary: SpacePrimitives, - secondary: SpacePrimitives, - ): Promise { - console.log("[sync]", "Starting conflict resolution for", name); - const filePieces = name.split("."); - const fileNameBase = filePieces.slice(0, -1).join("."); - const fileNameExt = filePieces[filePieces.length - 1]; - const pageData1 = await primary.readFile(name); - const pageData2 = await secondary.readFile(name); - - let byteWiseMatch = true; - const arrayBuffer1 = pageData1.data; - const arrayBuffer2 = pageData2.data; - if (arrayBuffer1.byteLength !== arrayBuffer2.byteLength) { - byteWiseMatch = false; - } - if (byteWiseMatch) { - // Byte-wise comparison - for (let i = 0; i < arrayBuffer1.byteLength; i++) { - if (arrayBuffer1[i] !== arrayBuffer2[i]) { - byteWiseMatch = false; - break; - } - } - // Byte wise they're still the same, so no confict - if (byteWiseMatch) { - console.log("[sync]", "Files are the same, no conflict"); - - snapshot.set(name, [ - pageData1.meta.lastModified, - pageData2.meta.lastModified, - ]); - return 0; - } - } - - let operations = 0; - const revisionFileName = filePieces.length === 1 - ? `${name}.conflicted.${pageData2.meta.lastModified}` - : `${fileNameBase}.conflicted.${pageData2.meta.lastModified}.${fileNameExt}`; - console.log( - "[sync]", - "Going to create conflicting copy", - revisionFileName, - ); - - // Copy secondary to conflict copy - const localConflictMeta = await primary.writeFile( - revisionFileName, - pageData2.data, - ); - operations++; - const remoteConflictMeta = await secondary.writeFile( - revisionFileName, - pageData2.data, - ); - operations++; - - // Write replacement on top - const writeMeta = await secondary.writeFile( - name, - pageData1.data, - true, - ); - operations++; - - // Updating snapshot - snapshot.set(revisionFileName, [ - localConflictMeta.lastModified, - remoteConflictMeta.lastModified, - ]); - - snapshot.set(name, [pageData1.meta.lastModified, writeMeta.lastModified]); - return operations; - } - syncCandidates(files: FileMeta[]): FileMeta[] { if (this.options.isSyncCandidate) { return files.filter((meta) => this.options.isSyncCandidate!(meta.name)); diff --git a/plug-api/syscalls/datastore.ts b/plug-api/syscalls/datastore.ts index 488361dd..a419ceeb 100644 --- a/plug-api/syscalls/datastore.ts +++ b/plug-api/syscalls/datastore.ts @@ -1,4 +1,4 @@ -import type { LuaCollectionQuery } from "../../web/space_lua/query_collection.ts"; +import type { LuaCollectionQuery } from "../../lib/space_lua/query_collection.ts"; import { syscall } from "../syscall.ts"; import type { KV, KvKey, KvQuery } from "../types.ts"; diff --git a/plug-api/syscalls/index.ts b/plug-api/syscalls/index.ts index f70e4d3a..382b95c2 100644 --- a/plug-api/syscalls/index.ts +++ b/plug-api/syscalls/index.ts @@ -1,5 +1,5 @@ import type { ObjectValue } from "@silverbulletmd/silverbullet/types"; -import type { LuaCollectionQuery } from "../../web/space_lua/query_collection.ts"; +import type { LuaCollectionQuery } from "../../lib/space_lua/query_collection.ts"; import { syscall } from "@silverbulletmd/silverbullet/syscall"; /** diff --git a/plug-api/syscalls/lua.ts b/plug-api/syscalls/lua.ts index e9f23043..ee663c1a 100644 --- a/plug-api/syscalls/lua.ts +++ b/plug-api/syscalls/lua.ts @@ -1,5 +1,5 @@ import { syscall } from "../syscall.ts"; -import type { LuaBlock, LuaExpression } from "../../web/space_lua/ast.ts"; +import type { LuaBlock, LuaExpression } from "../../lib/space_lua/ast.ts"; export function parse( code: string, diff --git a/plugs/README.md b/plugs/README.md index 6e1c7fb7..80426587 100644 --- a/plugs/README.md +++ b/plugs/README.md @@ -1 +1 @@ -This folder contains the implementation of SilverBullet's built-in plugs — the plugs that ship with SB itself. \ No newline at end of file +This folder contains the implementation of SilverBullet's built-in plugs — the plugs that ship with SB itself. \ No newline at end of file diff --git a/plugs/index/api.ts b/plugs/index/api.ts index 66469d65..6c02be02 100644 --- a/plugs/index/api.ts +++ b/plugs/index/api.ts @@ -1,7 +1,7 @@ import { datastore, markdown } from "@silverbulletmd/silverbullet/syscalls"; import type { KV, KvKey, KvQuery, ObjectValue } from "../../plug-api/types.ts"; import { ttlCache } from "$lib/memory_cache.ts"; -import type { LuaCollectionQuery } from "../../web/space_lua/query_collection.ts"; +import type { LuaCollectionQuery } from "../../lib/space_lua/query_collection.ts"; import { extractFrontmatter as extractFrontmatterFromTree, type FrontMatter, diff --git a/plugs/index/lint.ts b/plugs/index/lint.ts index 57ed01a3..ececace0 100644 --- a/plugs/index/lint.ts +++ b/plugs/index/lint.ts @@ -1,11 +1,10 @@ import { lua, YAML } from "@silverbulletmd/silverbullet/syscalls"; -import type { LintDiagnostic } from "../../plug-api/types.ts"; +import type { LintDiagnostic, LintEvent } from "../../plug-api/types.ts"; import { findNodeOfType, renderToText, traverseTreeAsync, } from "@silverbulletmd/silverbullet/lib/tree"; -import type { LintEvent } from "../../plug-api/types.ts"; export async function lintYAML({ tree }: LintEvent): Promise { const diagnostics: LintDiagnostic[] = []; diff --git a/plugs/index/paragraph.ts b/plugs/index/paragraph.ts index c1dab3a5..9560c69b 100644 --- a/plugs/index/paragraph.ts +++ b/plugs/index/paragraph.ts @@ -1,4 +1,4 @@ -import type { IndexTreeEvent } from "../../plug-api/types.ts"; +import type { IndexTreeEvent, ObjectValue } from "../../plug-api/types.ts"; import { indexObjects } from "./api.ts"; import { collectNodesOfType, @@ -7,7 +7,6 @@ import { traverseTreeAsync, } from "../../plug-api/lib/tree.ts"; import { extractAttributes } from "@silverbulletmd/silverbullet/lib/attribute"; -import type { ObjectValue } from "../../plug-api/types.ts"; import { updateITags } from "@silverbulletmd/silverbullet/lib/tags"; import { extractFrontmatter } from "@silverbulletmd/silverbullet/lib/frontmatter"; import { extractHashtag } from "../../plug-api/lib/tags.ts"; diff --git a/plugs/index/refactor.ts b/plugs/index/refactor.ts index fd00be6d..fba5dccd 100644 --- a/plugs/index/refactor.ts +++ b/plugs/index/refactor.ts @@ -11,13 +11,13 @@ import { folderName, } from "@silverbulletmd/silverbullet/lib/resolve"; import type { ObjectValue } from "@silverbulletmd/silverbullet/types"; +import type { ParseTree } from "@silverbulletmd/silverbullet/lib/tree"; import { addParentPointers, + findNodeOfType, findParentMatching, nodeAtPos, } from "@silverbulletmd/silverbullet/lib/tree"; -import type { ParseTree } from "@silverbulletmd/silverbullet/lib/tree"; -import { findNodeOfType } from "@silverbulletmd/silverbullet/lib/tree"; import { queryLuaObjects } from "./api.ts"; /** diff --git a/plugs/index/script.ts b/plugs/index/script.ts index 0ac51c4d..1bc5493b 100644 --- a/plugs/index/script.ts +++ b/plugs/index/script.ts @@ -1,7 +1,7 @@ -import type { IndexTreeEvent } from "../../plug-api/types.ts"; +import type { IndexTreeEvent, ObjectValue } from "../../plug-api/types.ts"; import { collectNodesOfType, findNodeOfType } from "../../plug-api/lib/tree.ts"; -import type { ObjectValue } from "../../plug-api/types.ts"; import { indexObjects } from "./api.ts"; + export type ScriptObject = ObjectValue<{ script: string; priority?: number; diff --git a/plugs/index/style.ts b/plugs/index/style.ts index 5c5a3453..be2acb2c 100644 --- a/plugs/index/style.ts +++ b/plugs/index/style.ts @@ -1,6 +1,5 @@ -import type { IndexTreeEvent } from "../../plug-api/types.ts"; +import type { IndexTreeEvent, ObjectValue } from "../../plug-api/types.ts"; import { collectNodesOfType, findNodeOfType } from "../../plug-api/lib/tree.ts"; -import type { ObjectValue } from "../../plug-api/types.ts"; import { indexObjects } from "./api.ts"; export type StyleObject = ObjectValue<{ diff --git a/plugs/markdown/assets/preview.js b/plugs/markdown/assets/preview.js index cb751734..f599a406 100644 --- a/plugs/markdown/assets/preview.js +++ b/plugs/markdown/assets/preview.js @@ -1,15 +1,15 @@ document.getElementById("root").addEventListener("click", (e) => { - // console.log("Got click", e.target) - const dataSet = e.target.dataset; - if (dataSet["onclick"]) { - syscall("event.dispatch", "preview:click", dataSet["onclick"]).catch((e) => - console.log("Error", e) - ); - } else if (dataSet["pos"]) { - syscall( - "event.dispatch", - "preview:click", - JSON.stringify(["pos", dataSet["pos"]]), - ).catch((e) => console.log("Error", e)); - } + // console.log("Got click", e.target) + const dataSet = e.target.dataset; + if (dataSet["onclick"]) { + syscall("event.dispatch", "preview:click", dataSet["onclick"]).catch((e) => + console.log("Error", e) + ); + } else if (dataSet["pos"]) { + syscall( + "event.dispatch", + "preview:click", + JSON.stringify(["pos", dataSet["pos"]]), + ).catch((e) => console.log("Error", e)); + } }); diff --git a/plugs/tasks/task.ts b/plugs/tasks/task.ts index 63b2466d..ca28d312 100644 --- a/plugs/tasks/task.ts +++ b/plugs/tasks/task.ts @@ -1,4 +1,8 @@ -import type { ClickEvent, IndexTreeEvent } from "../../plug-api/types.ts"; +import type { + ClickEvent, + IndexTreeEvent, + ObjectValue, +} from "../../plug-api/types.ts"; import { editor, @@ -26,7 +30,6 @@ import { extractAttributes, } from "@silverbulletmd/silverbullet/lib/attribute"; import { rewritePageRefs } from "@silverbulletmd/silverbullet/lib/resolve"; -import type { ObjectValue } from "../../plug-api/types.ts"; import { indexObjects } from "../index/plug_api.ts"; import { cleanHashTags, diff --git a/scripts/generate.sh b/scripts/generate.sh index 30fe462c..23c23d34 100755 --- a/scripts/generate.sh +++ b/scripts/generate.sh @@ -1,6 +1,6 @@ #!/bin/sh -e -LUA_GRAMMAR=common/space_lua/lua.grammar +LUA_GRAMMAR=lib/space_lua/lua.grammar LEZER_GENERATOR_VERSION=1.5.1 -deno run -A npm:@lezer/generator@$LEZER_GENERATOR_VERSION $LUA_GRAMMAR -o common/space_lua/parse-lua.js \ No newline at end of file +deno run -A npm:@lezer/generator@$LEZER_GENERATOR_VERSION $LUA_GRAMMAR -o lib/space_lua/parse-lua.js \ No newline at end of file diff --git a/server/http_server.ts b/server/http_server.ts index 936044c6..09569ee2 100644 --- a/server/http_server.ts +++ b/server/http_server.ts @@ -224,6 +224,13 @@ export class HttpServer { }); } + stop() { + if (this.abortController) { + this.abortController.abort(); + console.log("stopped server"); + } + } + private addAuth() { const excludedPaths = [ "/manifest.json", @@ -654,13 +661,6 @@ export class HttpServer { private unprefixedUrl(url: string): string { return removeUrlPrefix(url, this.options.hostUrlPrefix); } - - stop() { - if (this.abortController) { - this.abortController.abort(); - console.log("stopped server"); - } - } } function utcDateString(mtime: number): string { diff --git a/server/shell_backend.ts b/server/shell_backend.ts index 4c353a3e..7aca033b 100644 --- a/server/shell_backend.ts +++ b/server/shell_backend.ts @@ -44,7 +44,8 @@ export class StreamingShell { private textDecoder = new TextDecoder(); private textEncoder = new TextEncoder(); - constructor(private cwd: string) {} + constructor(private cwd: string) { + } start(cmd: string, args: string[]): void { console.log("Starting streaming shell:", cmd, args); @@ -76,33 +77,6 @@ export class StreamingShell { }); } - private async readStream( - stream: ReadableStream, - type: string, - ): Promise { - const reader = stream.getReader(); - - try { - while (true) { - const { value, done } = await reader.read(); - if (done) break; - - // Handle as text data (UTF-8) - const text = this.textDecoder.decode(value, { stream: true }); - if (text.trim()) { - console.log(`${type.toUpperCase()}: ${text}`); - this.notifyOutput(type, "text", text); - } - } - } catch (e) { - const error = e as Error; - console.error(`Error reading ${type}:`, error); - this.notifyOutput("error", "text", `Stream error: ${error.message}`); - } finally { - reader.releaseLock(); - } - } - async writeToStdin(data: string): Promise { if (!this.process || !this.process.stdin) { throw new Error("Process not started or stdin not available"); @@ -123,12 +97,6 @@ export class StreamingShell { this.outputHandlers.push(handler); } - private notifyOutput(type: string, _format: string, data: string): void { - for (const handler of this.outputHandlers) { - handler(type, data); - } - } - /** * Send a signal to the process * @param signal The signal to send (e.g., "SIGTERM", "SIGINT", "SIGKILL") @@ -242,6 +210,39 @@ export class StreamingShell { this.process = null; } } + + private async readStream( + stream: ReadableStream, + type: string, + ): Promise { + const reader = stream.getReader(); + + try { + while (true) { + const { value, done } = await reader.read(); + if (done) break; + + // Handle as text data (UTF-8) + const text = this.textDecoder.decode(value, { stream: true }); + if (text.trim()) { + console.log(`${type.toUpperCase()}: ${text}`); + this.notifyOutput(type, "text", text); + } + } + } catch (e) { + const error = e as Error; + console.error(`Error reading ${type}:`, error); + this.notifyOutput("error", "text", `Stream error: ${error.message}`); + } finally { + reader.releaseLock(); + } + } + + private notifyOutput(type: string, _format: string, data: string): void { + for (const handler of this.outputHandlers) { + handler(type, data); + } + } } export class LocalShell implements ShellBackend { diff --git a/server/space_server.ts b/server/space_server.ts index 30f83dc1..1a849bad 100644 --- a/server/space_server.ts +++ b/server/space_server.ts @@ -5,8 +5,8 @@ import type { SpacePrimitives } from "../lib/spaces/space_primitives.ts"; import type { AssetBundle } from "../lib/asset_bundle/bundle.ts"; import { JWTIssuer } from "./crypto.ts"; import { compile as gitIgnoreCompiler } from "gitignore-parser"; -import { determineShellBackend, NotSupportedShell } from "./shell_backend.ts"; import type { ShellBackend } from "./shell_backend.ts"; +import { determineShellBackend, NotSupportedShell } from "./shell_backend.ts"; import { determineStorageBackend } from "./storage_backend.ts"; import type { ServerOptions } from "./http_server.ts"; import type { AuthOptions } from "../cmd/server.ts"; diff --git a/web/auth.html b/web/auth.html index dbe18357..e371b5a9 100644 --- a/web/auth.html +++ b/web/auth.html @@ -2,223 +2,223 @@ - - - - - Login to SilverBullet - + /* Add styles for checkbox alignment */ + .checkbox-wrapper { + display: flex; + align-items: center; + gap: 0.5rem; + flex-direction: row; + } + + .checkbox-wrapper input[type="checkbox"] { + margin: 0; + } + -
+
-

- Login to - SilverBullet -

-
-
-
- - -
-
- - -
-
- - -
-
- -
-
- +

+ Login to + SilverBullet +

+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
-
+
- + .then(response => { + if (!response.ok) { + throw new Error('Failed to load manifest'); + } + return response.json(); + }) + .then(manifest => { + const appName = manifest.name || 'SilverBullet'; + // Update the title + document.title = `Login to ${appName}`; + // Update the header text + const header = document.querySelector('h1'); + if (header) { + header.innerHTML = `Login to ${appName}`; + } + }) + .catch(error => { + console.error('Error fetching manifest:', error); + }); + \ No newline at end of file diff --git a/web/client.ts b/web/client.ts index 26d8a0bb..4cac8177 100644 --- a/web/client.ts +++ b/web/client.ts @@ -28,16 +28,16 @@ import type { PageCreatingContent, PageCreatingEvent, SlashCompletions, -} from "../plug-api/types.ts"; +} from "@silverbulletmd/silverbullet/types"; import type { StyleObject } from "../plugs/index/style.ts"; import { throttle } from "$lib/async.ts"; -import { PlugSpacePrimitives } from "../lib/spaces/plug_space_primitives.ts"; -import { EventedSpacePrimitives } from "../lib/spaces/evented_space_primitives.ts"; +import { PlugSpacePrimitives } from "$lib/spaces/plug_space_primitives.ts"; +import { EventedSpacePrimitives } from "$lib/spaces/evented_space_primitives.ts"; import { pageSyncInterval, SyncService } from "./sync_service.ts"; import { simpleHash } from "$lib/crypto.ts"; -import type { SyncStatus } from "../lib/spaces/sync.ts"; -import { HttpSpacePrimitives } from "../lib/spaces/http_space_primitives.ts"; -import { FallbackSpacePrimitives } from "../lib/spaces/fallback_space_primitives.ts"; +import type { SyncStatus } from "$lib/spaces/sync.ts"; +import { HttpSpacePrimitives } from "$lib/spaces/http_space_primitives.ts"; +import { FallbackSpacePrimitives } from "$lib/spaces/fallback_space_primitives.ts"; import { encodePageURI, encodeRef, @@ -49,23 +49,23 @@ import { ClientSystem } from "./client_system.ts"; import { createEditorState } from "./editor_state.ts"; import { MainUI } from "./editor_ui.tsx"; import { cleanPageRef } from "@silverbulletmd/silverbullet/lib/resolve"; -import type { SpacePrimitives } from "../lib/spaces/space_primitives.ts"; +import type { SpacePrimitives } from "$lib/spaces/space_primitives.ts"; import type { CodeWidgetButton, FileMeta, PageMeta, -} from "../plug-api/types.ts"; +} from "@silverbulletmd/silverbullet/types"; import { DataStore } from "$lib/data/datastore.ts"; import { IndexedDBKvPrimitives } from "$lib/data/indexeddb_kv_primitives.ts"; import { DataStoreMQ } from "$lib/data/mq.datastore.ts"; -import { DataStoreSpacePrimitives } from "../lib/spaces/datastore_space_primitives.ts"; +import { DataStoreSpacePrimitives } from "$lib/spaces/datastore_space_primitives.ts"; -import { ReadOnlySpacePrimitives } from "../lib/spaces/ro_space_primitives.ts"; +import { ReadOnlySpacePrimitives } from "$lib/spaces/ro_space_primitives.ts"; import { LimitedMap } from "$lib/limited_map.ts"; -import { plugPrefix } from "../lib/spaces/constants.ts"; +import { plugPrefix } from "$lib/spaces/constants.ts"; import { diffAndPrepareChanges } from "./cm_util.ts"; import { DocumentEditor } from "./document_editor.ts"; -import { parseExpressionString } from "./space_lua/parse.ts"; +import { parseExpressionString } from "$lib/space_lua/parse.ts"; import { Config } from "./config.ts"; const frontMatterRegex = /^---\n(([^\n]|\n)*?)---\n/; @@ -117,27 +117,42 @@ export class Client { // Document editor documentEditor: DocumentEditor | null = null; - - private pageNavigator!: PathPageNavigator; - - private dbPrefix: string; - saveTimeout?: number; debouncedUpdateEvent = throttle(() => { this.eventHook .dispatchEvent("editor:updated") .catch((e) => console.error("Error dispatching editor:updated event", e)); }, 1000); - - // Sync related stuff // Track if plugs have been updated since sync cycle fullSyncCompleted = false; syncService!: SyncService; - private onLoadRef: Ref; - + // Sync related stuff // Set to true once the system is ready (plugs loaded) public systemReady: boolean = false; + private pageNavigator!: PathPageNavigator; + private dbPrefix: string; + private onLoadRef: Ref; + // Progress circle handling + private progressTimeout?: number; + // Widget and image height caching + private widgetCache = new LimitedMap(100); // bodyText -> WidgetCacheItem + debouncedWidgetCacheFlush = throttle(() => { + this.ds.set(["cache", "widgets"], this.widgetCache.toJSON()) + .catch( + console.error, + ); + }, 2000); + private widgetHeightCache = new LimitedMap(100); // bodytext -> height + debouncedWidgetHeightCacheFlush = throttle(() => { + this.ds.set( + ["cache", "widgetHeight"], + this.widgetHeightCache.toJSON(), + ) + .catch( + console.error, + ); + }, 2000); constructor( private parent: Element, @@ -153,6 +168,13 @@ export class Client { this.onLoadRef = parseRefFromURI(); } + // Note: This is a legacy method, which only makes sense when the current editor is a page editor + get currentPage(): string { + return this.ui.viewState.current !== undefined + ? this.ui.viewState.current.path + : this.onLoadRef.page; // best effort + } + /** * Initialize the client * This is a separated from the constructor to allow for async initialization @@ -272,209 +294,6 @@ export class Client { return this.syncService.hasInitialSyncCompleted(); } - private async initSync() { - this.syncService.start(); - - // We're still booting, if a initial sync has already been completed we know this is the initial sync - let initialSync = !await this.hasInitialSyncCompleted(); - - this.eventHook.addLocalListener("sync:success", async (operations) => { - if (operations > 0) { - // Update the page list - await this.space.updatePageList(); - } - if (operations !== undefined) { - // "sync:success" is called with a number of operations only from syncSpace(), not from syncing individual pages - this.fullSyncCompleted = true; - - console.log("[sync]", "Full sync completed"); - - // A full sync just completed - if (!initialSync) { - // If this was NOT the initial sync let's check if we need to perform a space reindex - this.clientSystem.ensureFullIndex().catch( - console.error, - ); - } else { // initialSync - console.log( - "[sync]", - "Initial sync completed, now need to do a full space index to ensure all pages are indexed using any custom indexers", - ); - this.space.spacePrimitives.enablePageEvents = true; - this.clientSystem.ensureFullIndex().catch( - console.error, - ); - initialSync = false; - } - } - if (operations) { - // Likely initial sync so let's show visually that we're synced now - this.showProgress(100, "sync"); - } - - this.ui.viewDispatch({ type: "sync-change", syncSuccess: true }); - }); - - this.eventHook.addLocalListener("sync:error", (_name) => { - this.ui.viewDispatch({ type: "sync-change", syncSuccess: false }); - }); - - this.eventHook.addLocalListener("sync:conflict", (name) => { - this.flashNotification( - `Sync: conflict detected for ${name} - conflict copy created`, - "error", - ); - }); - - this.eventHook.addLocalListener("sync:progress", (status: SyncStatus) => { - this.showProgress( - Math.round(status.filesProcessed / status.totalFiles * 100), - "sync", - ); - }); - - this.eventHook.addLocalListener( - "file:synced", - (meta: FileMeta, direction: string) => { - if (direction === "secondary->primary") { - // We likely polled the currently open page or document which triggered a local update, let's update the editor accordingly - this.space.spacePrimitives.getFileMeta(meta.name); - } - }, - ); - } - - private navigateWithinPage(pageState: LocationState) { - if (pageState.kind === "document") return; - - // Did we end up doing anything in terms of internal navigation? - let adjustedPosition = false; - - // Was a particular scroll position persisted? - if ( - pageState.scrollTop !== undefined && - !(pageState.scrollTop === 0 && - (pageState.pos !== undefined || pageState.header !== undefined)) - ) { - setTimeout(() => { - this.editorView.scrollDOM.scrollTop = pageState.scrollTop!; - }); - adjustedPosition = true; - } - - // Was a particular cursor/selection set? - if ( - pageState.selection?.anchor && !pageState.pos && - !pageState.header - ) { // Only do this if we got a specific cursor position - console.log("Changing cursor position to", pageState.selection); - this.editorView.dispatch({ - selection: pageState.selection, - }); - adjustedPosition = true; - } - - // Was there a pos set? - let pos: number | { line: number; column: number } | undefined = - pageState.pos; - - if (pageState.header) { - console.log("Navigating to header", pageState.header); - const pageText = this.editorView.state.sliceDoc(); - - // This is somewhat of a simplistic way to find the header, but it works for now - pos = pageText.indexOf(`# ${pageState.header}\n`) + 2; - - if (pos === -1) { - return this.flashNotification( - `Could not find header "${pageState.header}"`, - "error", - ); - } - - adjustedPosition = true; - } - if (pos !== undefined) { - // Translate line and column number to position in text - if (pos instanceof Object) { - // CodeMirror already keeps information about lines - const cmLine = this.editorView.state.doc.line(pos.line); - // How much to move inside the line, column number starts from 1 - const offset = Math.max(0, Math.min(cmLine.length, pos.column - 1)); - pos = cmLine.from + offset; - } - - this.editorView.dispatch({ - selection: { anchor: pos! }, - effects: EditorView.scrollIntoView(pos!, { - y: "start", - yMargin: 5, - }), - }); - adjustedPosition = true; - } - - // If not: just put the cursor at the top of the page, right after the frontmatter - if (!adjustedPosition) { - // Somewhat ad-hoc way to determine if the document contains frontmatter and if so, putting the cursor _after it_. - const pageText = this.editorView.state.sliceDoc(); - - // Default the cursor to be at position 0 - let initialCursorPos = 0; - const match = frontMatterRegex.exec(pageText); - if (match) { - // Frontmatter found, put cursor after it - initialCursorPos = match[0].length; - } - // By default scroll to the top - this.editorView.scrollDOM.scrollTop = 0; - this.editorView.dispatch({ - selection: { anchor: initialCursorPos }, - // And then scroll down if required - scrollIntoView: true, - }); - } - } - - private async initNavigator() { - this.pageNavigator = new PathPageNavigator(this); - - this.pageNavigator.subscribe(async (locationState) => { - console.log("Now navigating to", locationState); - - if (locationState.kind === "page") { - await this.loadPage(locationState.page); - } else { - await this.loadDocumentEditor(locationState.page); - } - - // Setup scroll position, cursor position, etc - this.navigateWithinPage(locationState); - - // Persist this page as the last opened page, we'll use this for cold start PWA loads - await this.ds.set( - ["client", "lastOpenedPath"], - locationState.page, - ); - }); - - if (location.hash === "#boot") { - // Cold start PWA load - const lastPath = await this.ds.get([ - "client", - "lastOpenedPath", - ]); - if (lastPath) { - console.log("Navigating to last opened page", lastPath.path); - await this.navigate(parseRef(lastPath)); - } - } - setTimeout(() => { - console.log("Focusing editor"); - this.focus(); - }, 100); - } - initSpace(): SpacePrimitives { this.httpSpacePrimitives = new HttpSpacePrimitives( document.baseURI.replace(/\/*$/, ""), @@ -587,13 +406,6 @@ export class Client { return localSpacePrimitives; } - // Note: This is a legacy method, which only makes sense when the current editor is a page editor - get currentPage(): string { - return this.ui.viewState.current !== undefined - ? this.ui.viewState.current.path - : this.onLoadRef.page; // best effort - } - currentPath(extension: boolean = false): string { if (this.ui.viewState.current !== undefined) { return this.ui.viewState.current.path + @@ -823,9 +635,6 @@ export class Client { }); } - // Progress circle handling - private progressTimeout?: number; - showProgress(progressPercentage?: number, progressType?: "sync" | "index") { this.ui.viewDispatch({ type: "set-progress", @@ -1528,10 +1337,6 @@ export class Client { return; } - // Widget and image height caching - private widgetCache = new LimitedMap(100); // bodyText -> WidgetCacheItem - private widgetHeightCache = new LimitedMap(100); // bodytext -> height - async loadCaches() { const [widgetHeightCache, widgetCache] = await this .ds.batchGet([[ @@ -1542,31 +1347,15 @@ export class Client { this.widgetCache = new LimitedMap(100, widgetCache || {}); } - debouncedWidgetHeightCacheFlush = throttle(() => { - this.ds.set( - ["cache", "widgetHeight"], - this.widgetHeightCache.toJSON(), - ) - .catch( - console.error, - ); - }, 2000); - setCachedWidgetHeight(bodyText: string, height: number) { this.widgetHeightCache.set(bodyText, height); this.debouncedWidgetHeightCacheFlush(); } + getCachedWidgetHeight(bodyText: string): number { return this.widgetHeightCache.get(bodyText) ?? -1; } - debouncedWidgetCacheFlush = throttle(() => { - this.ds.set(["cache", "widgets"], this.widgetCache.toJSON()) - .catch( - console.error, - ); - }, 2000); - setWidgetCache(key: string, cacheItem: WidgetCacheItem) { this.widgetCache.set(key, cacheItem); this.debouncedWidgetCacheFlush(); @@ -1575,4 +1364,207 @@ export class Client { getWidgetCache(key: string): WidgetCacheItem | undefined { return this.widgetCache.get(key); } + + private async initSync() { + this.syncService.start(); + + // We're still booting, if a initial sync has already been completed we know this is the initial sync + let initialSync = !await this.hasInitialSyncCompleted(); + + this.eventHook.addLocalListener("sync:success", async (operations) => { + if (operations > 0) { + // Update the page list + await this.space.updatePageList(); + } + if (operations !== undefined) { + // "sync:success" is called with a number of operations only from syncSpace(), not from syncing individual pages + this.fullSyncCompleted = true; + + console.log("[sync]", "Full sync completed"); + + // A full sync just completed + if (!initialSync) { + // If this was NOT the initial sync let's check if we need to perform a space reindex + this.clientSystem.ensureFullIndex().catch( + console.error, + ); + } else { // initialSync + console.log( + "[sync]", + "Initial sync completed, now need to do a full space index to ensure all pages are indexed using any custom indexers", + ); + this.space.spacePrimitives.enablePageEvents = true; + this.clientSystem.ensureFullIndex().catch( + console.error, + ); + initialSync = false; + } + } + if (operations) { + // Likely initial sync so let's show visually that we're synced now + this.showProgress(100, "sync"); + } + + this.ui.viewDispatch({ type: "sync-change", syncSuccess: true }); + }); + + this.eventHook.addLocalListener("sync:error", (_name) => { + this.ui.viewDispatch({ type: "sync-change", syncSuccess: false }); + }); + + this.eventHook.addLocalListener("sync:conflict", (name) => { + this.flashNotification( + `Sync: conflict detected for ${name} - conflict copy created`, + "error", + ); + }); + + this.eventHook.addLocalListener("sync:progress", (status: SyncStatus) => { + this.showProgress( + Math.round(status.filesProcessed / status.totalFiles * 100), + "sync", + ); + }); + + this.eventHook.addLocalListener( + "file:synced", + (meta: FileMeta, direction: string) => { + if (direction === "secondary->primary") { + // We likely polled the currently open page or document which triggered a local update, let's update the editor accordingly + this.space.spacePrimitives.getFileMeta(meta.name); + } + }, + ); + } + + private navigateWithinPage(pageState: LocationState) { + if (pageState.kind === "document") return; + + // Did we end up doing anything in terms of internal navigation? + let adjustedPosition = false; + + // Was a particular scroll position persisted? + if ( + pageState.scrollTop !== undefined && + !(pageState.scrollTop === 0 && + (pageState.pos !== undefined || pageState.header !== undefined)) + ) { + setTimeout(() => { + this.editorView.scrollDOM.scrollTop = pageState.scrollTop!; + }); + adjustedPosition = true; + } + + // Was a particular cursor/selection set? + if ( + pageState.selection?.anchor && !pageState.pos && + !pageState.header + ) { // Only do this if we got a specific cursor position + console.log("Changing cursor position to", pageState.selection); + this.editorView.dispatch({ + selection: pageState.selection, + }); + adjustedPosition = true; + } + + // Was there a pos set? + let pos: number | { line: number; column: number } | undefined = + pageState.pos; + + if (pageState.header) { + console.log("Navigating to header", pageState.header); + const pageText = this.editorView.state.sliceDoc(); + + // This is somewhat of a simplistic way to find the header, but it works for now + pos = pageText.indexOf(`# ${pageState.header}\n`) + 2; + + if (pos === -1) { + return this.flashNotification( + `Could not find header "${pageState.header}"`, + "error", + ); + } + + adjustedPosition = true; + } + if (pos !== undefined) { + // Translate line and column number to position in text + if (pos instanceof Object) { + // CodeMirror already keeps information about lines + const cmLine = this.editorView.state.doc.line(pos.line); + // How much to move inside the line, column number starts from 1 + const offset = Math.max(0, Math.min(cmLine.length, pos.column - 1)); + pos = cmLine.from + offset; + } + + this.editorView.dispatch({ + selection: { anchor: pos! }, + effects: EditorView.scrollIntoView(pos!, { + y: "start", + yMargin: 5, + }), + }); + adjustedPosition = true; + } + + // If not: just put the cursor at the top of the page, right after the frontmatter + if (!adjustedPosition) { + // Somewhat ad-hoc way to determine if the document contains frontmatter and if so, putting the cursor _after it_. + const pageText = this.editorView.state.sliceDoc(); + + // Default the cursor to be at position 0 + let initialCursorPos = 0; + const match = frontMatterRegex.exec(pageText); + if (match) { + // Frontmatter found, put cursor after it + initialCursorPos = match[0].length; + } + // By default scroll to the top + this.editorView.scrollDOM.scrollTop = 0; + this.editorView.dispatch({ + selection: { anchor: initialCursorPos }, + // And then scroll down if required + scrollIntoView: true, + }); + } + } + + private async initNavigator() { + this.pageNavigator = new PathPageNavigator(this); + + this.pageNavigator.subscribe(async (locationState) => { + console.log("Now navigating to", locationState); + + if (locationState.kind === "page") { + await this.loadPage(locationState.page); + } else { + await this.loadDocumentEditor(locationState.page); + } + + // Setup scroll position, cursor position, etc + this.navigateWithinPage(locationState); + + // Persist this page as the last opened page, we'll use this for cold start PWA loads + await this.ds.set( + ["client", "lastOpenedPath"], + locationState.page, + ); + }); + + if (location.hash === "#boot") { + // Cold start PWA load + const lastPath = await this.ds.get([ + "client", + "lastOpenedPath", + ]); + if (lastPath) { + console.log("Navigating to last opened page", lastPath.path); + await this.navigate(parseRef(lastPath)); + } + } + setTimeout(() => { + console.log("Focusing editor"); + this.focus(); + }, 100); + } } diff --git a/web/client_system.ts b/web/client_system.ts index 4ca8bd63..d029b9e9 100644 --- a/web/client_system.ts +++ b/web/client_system.ts @@ -42,7 +42,7 @@ import { commandSyscalls } from "./syscalls/command.ts"; import { configSyscalls } from "./syscalls/config.ts"; import { eventListenerSyscalls } from "./syscalls/event.ts"; import { DocumentEditorHook } from "./hooks/document_editor.ts"; -import type { LuaCollectionQuery } from "./space_lua/query_collection.ts"; +import type { LuaCollectionQuery } from "../lib/space_lua/query_collection.ts"; import type { AppCommand } from "$lib/command.ts"; import { ScriptEnvironment } from "./space_script.ts"; import { SpaceLuaEnvironment } from "./space_lua.ts"; @@ -72,6 +72,7 @@ export class ClientSystem { scriptEnv: ScriptEnvironment = new ScriptEnvironment(); spaceLuaEnv = new SpaceLuaEnvironment(); scriptsLoaded: boolean = false; + private indexOngoing = false; constructor( private client: Client, @@ -273,8 +274,6 @@ export class ClientSystem { ); } - private indexOngoing = false; - async ensureFullIndex() { const currentIndexVersion = await this.getCurrentIndexVersion(); @@ -306,6 +305,10 @@ export class ClientSystem { } } + public async hasFullIndexCompleted() { + return (await this.ds.get(indexVersionKey)) === desiredIndexVersion; + } + private getCurrentIndexVersion() { return this.ds.get(indexVersionKey); } @@ -313,8 +316,4 @@ export class ClientSystem { private async markFullSpaceIndexComplete() { await this.ds.set(indexVersionKey, desiredIndexVersion); } - - public async hasFullIndexCompleted() { - return (await this.ds.get(indexVersionKey)) === desiredIndexVersion; - } } diff --git a/web/cm_plugins/code_widget.ts b/web/cm_plugins/code_widget.ts index 0b4b6054..377949c6 100644 --- a/web/cm_plugins/code_widget.ts +++ b/web/cm_plugins/code_widget.ts @@ -2,6 +2,7 @@ export const activeWidgets = new Set(); export interface DomWidget { dom?: HTMLElement; + renderContent( div: HTMLElement, cachedHtml: string | undefined, diff --git a/web/cm_plugins/iframe_widget.ts b/web/cm_plugins/iframe_widget.ts index 9ca664ec..888a74c5 100644 --- a/web/cm_plugins/iframe_widget.ts +++ b/web/cm_plugins/iframe_widget.ts @@ -19,6 +19,12 @@ export class IFrameWidget extends WidgetType { super(); } + override get estimatedHeight(): number { + const cachedHeight = this.client.getCachedWidgetHeight(this.bodyText); + // console.log("Calling estimated height", this.bodyText, cachedHeight); + return cachedHeight > 0 ? cachedHeight : 150; + } + toDOM(): HTMLElement { const from = this.from; const iframe = createWidgetSandboxIFrame( @@ -67,12 +73,6 @@ export class IFrameWidget extends WidgetType { return iframe; } - override get estimatedHeight(): number { - const cachedHeight = this.client.getCachedWidgetHeight(this.bodyText); - // console.log("Calling estimated height", this.bodyText, cachedHeight); - return cachedHeight > 0 ? cachedHeight : 150; - } - override eq(other: WidgetType): boolean { return ( other instanceof IFrameWidget && diff --git a/web/cm_plugins/inline_content.ts b/web/cm_plugins/inline_content.ts index c4b53865..705ef611 100644 --- a/web/cm_plugins/inline_content.ts +++ b/web/cm_plugins/inline_content.ts @@ -31,11 +31,6 @@ class InlineContentWidget extends WidgetType { super(); } - override eq(other: InlineContentWidget) { - return other.url === this.url && other.title === this.title && - JSON.stringify(other.dim) === JSON.stringify(this.dim); - } - override get estimatedHeight(): number { const cachedHeight = this.client.getCachedWidgetHeight( `content:${this.url}`, @@ -43,6 +38,11 @@ class InlineContentWidget extends WidgetType { return cachedHeight; } + override eq(other: InlineContentWidget) { + return other.url === this.url && other.title === this.title && + JSON.stringify(other.dim) === JSON.stringify(this.dim); + } + toDOM() { const div = document.createElement("div"); div.className = "sb-inline-content"; diff --git a/web/cm_plugins/list.ts b/web/cm_plugins/list.ts index 871375ac..e5c919ac 100644 --- a/web/cm_plugins/list.ts +++ b/web/cm_plugins/list.ts @@ -52,6 +52,7 @@ class ListBulletWidget extends WidgetType { constructor(readonly bullet: string) { super(); } + toDOM(): HTMLElement { const listBullet = document.createElement("span"); listBullet.textContent = this.bullet; diff --git a/web/cm_plugins/lua_directive.ts b/web/cm_plugins/lua_directive.ts index be281856..6af0c977 100644 --- a/web/cm_plugins/lua_directive.ts +++ b/web/cm_plugins/lua_directive.ts @@ -7,16 +7,19 @@ import { isCursorInRange, } from "./util.ts"; import type { Client } from "../client.ts"; -import { parse as parseLua } from "../space_lua/parse.ts"; -import type { LuaBlock, LuaFunctionCallStatement } from "../space_lua/ast.ts"; -import { evalExpression } from "../space_lua/eval.ts"; +import { parse as parseLua } from "../../lib/space_lua/parse.ts"; +import type { + LuaBlock, + LuaFunctionCallStatement, +} from "../../lib/space_lua/ast.ts"; +import { evalExpression } from "../../lib/space_lua/eval.ts"; import { LuaEnv, + LuaRuntimeError, LuaStackFrame, luaValueToJS, singleResult, -} from "../space_lua/runtime.ts"; -import { LuaRuntimeError } from "../space_lua/runtime.ts"; +} from "../../lib/space_lua/runtime.ts"; import { encodeRef } from "@silverbulletmd/silverbullet/lib/page_ref"; import { resolveASTReference } from "../space_lua.ts"; import { LuaWidget } from "./lua_widget.ts"; diff --git a/web/cm_plugins/lua_widget.ts b/web/cm_plugins/lua_widget.ts index 370d5eef..48aba7c5 100644 --- a/web/cm_plugins/lua_widget.ts +++ b/web/cm_plugins/lua_widget.ts @@ -13,7 +13,7 @@ import { moveCursorIntoText, } from "./widget_util.ts"; import { expandMarkdown } from "../markdown.ts"; -import { LuaStackFrame, LuaTable } from "../space_lua/runtime.ts"; +import { LuaStackFrame, LuaTable } from "../../lib/space_lua/runtime.ts"; import { isBlockMarkdown, jsonToMDTable } from "../markdown_util.ts"; import { activeWidgets } from "./code_widget.ts"; @@ -55,6 +55,11 @@ export class LuaWidget extends WidgetType { super(); } + override get estimatedHeight(): number { + const cacheItem = this.client.getWidgetCache(this.cacheKey); + return cacheItem ? cacheItem.height : -1; + } + toDOM(): HTMLElement { const wrapperSpan = document.createElement("span"); wrapperSpan.className = "sb-lua-wrapper"; @@ -260,11 +265,6 @@ export class LuaWidget extends WidgetType { return container; } - override get estimatedHeight(): number { - const cacheItem = this.client.getWidgetCache(this.cacheKey); - return cacheItem ? cacheItem.height : -1; - } - override eq(other: WidgetType): boolean { return ( other instanceof LuaWidget && diff --git a/web/cm_plugins/table.ts b/web/cm_plugins/table.ts index ec837916..de28e44f 100644 --- a/web/cm_plugins/table.ts +++ b/web/cm_plugins/table.ts @@ -16,10 +16,11 @@ import { resolvePath, } from "@silverbulletmd/silverbullet/lib/resolve"; import { expandMarkdown } from "../markdown.ts"; -import { LuaStackFrame } from "../space_lua/runtime.ts"; +import { LuaStackFrame } from "../../lib/space_lua/runtime.ts"; class TableViewWidget extends WidgetType { tableBodyText: string; + constructor( readonly pos: number, readonly client: Client, @@ -29,6 +30,14 @@ class TableViewWidget extends WidgetType { this.tableBodyText = renderToText(t); } + override get estimatedHeight(): number { + const height = this.client.getCachedWidgetHeight( + `table:${this.tableBodyText}`, + ); + // console.log("Calling estimated height for table", height); + return height; + } + toDOM(): HTMLElement { const dom = document.createElement("span"); dom.classList.add("sb-table-widget"); @@ -76,14 +85,6 @@ class TableViewWidget extends WidgetType { return dom; } - override get estimatedHeight(): number { - const height = this.client.getCachedWidgetHeight( - `table:${this.tableBodyText}`, - ); - // console.log("Calling estimated height for table", height); - return height; - } - override eq(other: WidgetType): boolean { return ( other instanceof TableViewWidget && diff --git a/web/cm_plugins/task.ts b/web/cm_plugins/task.ts index 9609e833..69eb24bb 100644 --- a/web/cm_plugins/task.ts +++ b/web/cm_plugins/task.ts @@ -14,6 +14,7 @@ class CheckboxWidget extends WidgetType { ) { super(); } + toDOM(): HTMLElement { const wrap = document.createElement("span"); wrap.classList.add("sb-checkbox"); diff --git a/web/cm_plugins/util.ts b/web/cm_plugins/util.ts index 0b229534..61f7e5c2 100644 --- a/web/cm_plugins/util.ts +++ b/web/cm_plugins/util.ts @@ -26,6 +26,7 @@ export class LinkWidget extends WidgetType { ) { super(); } + toDOM(): HTMLElement { const anchor = document.createElement("a"); anchor.className = this.options.cssClass; @@ -80,6 +81,7 @@ export class HtmlWidget extends WidgetType { ) { super(); } + toDOM(): HTMLElement { const el = document.createElement("span"); if (this.className) { @@ -119,6 +121,7 @@ export class ButtonWidget extends WidgetType { ) { super(); } + toDOM(): HTMLElement { const anchor = document.createElement("button"); anchor.className = this.cssClass; diff --git a/web/commands.ts b/web/commands.ts index 04663c9d..08038e7a 100644 --- a/web/commands.ts +++ b/web/commands.ts @@ -1,9 +1,9 @@ import { EditorSelection, type StateCommand, + Text, Transaction, } from "@codemirror/state"; -import { Text } from "@codemirror/state"; export function insertMarker(marker: string): StateCommand { return ({ state, dispatch }) => { diff --git a/web/config.ts b/web/config.ts index 6ec64a1a..969e19e7 100644 --- a/web/config.ts +++ b/web/config.ts @@ -38,47 +38,6 @@ export class Config { this.schemas[key] = schema; } - /** - * Resolves a dot-notation path to the containing object and final key - * @param path The path to resolve (e.g. "foo.bar.baz") - * @param create Whether to create objects along the path if they don't exist - * @returns The containing object and the final key, or null if the path cannot be resolved - */ - private resolvePath( - path: string, - create = false, - ): { obj: any; key: string } | null { - if (!path.includes(".")) { - return { obj: this.values, key: path }; - } - - const parts = path.split("."); - const lastKey = parts.pop()!; - - let current = this.values; - - for (const part of parts) { - if (current[part] === undefined) { - if (create) { - current[part] = {}; - } else { - return null; - } - } else if (typeof current[part] !== "object" || current[part] === null) { - if (create) { - // Convert primitive to object if we're creating the path - current[part] = {}; - } else { - return null; - } - } - - current = current[part]; - } - - return { obj: current, key: lastKey }; - } - /** * Gets a value from the config * @param path The path to get, supports dot notation (e.g. "foo.bar.baz") @@ -155,4 +114,45 @@ export class Config { keys(): string[] { return Object.keys(this.values); } + + /** + * Resolves a dot-notation path to the containing object and final key + * @param path The path to resolve (e.g. "foo.bar.baz") + * @param create Whether to create objects along the path if they don't exist + * @returns The containing object and the final key, or null if the path cannot be resolved + */ + private resolvePath( + path: string, + create = false, + ): { obj: any; key: string } | null { + if (!path.includes(".")) { + return { obj: this.values, key: path }; + } + + const parts = path.split("."); + const lastKey = parts.pop()!; + + let current = this.values; + + for (const part of parts) { + if (current[part] === undefined) { + if (create) { + current[part] = {}; + } else { + return null; + } + } else if (typeof current[part] !== "object" || current[part] === null) { + if (create) { + // Convert primitive to object if we're creating the path + current[part] = {}; + } else { + return null; + } + } + + current = current[part]; + } + + return { obj: current, key: lastKey }; + } } diff --git a/web/document_editor.ts b/web/document_editor.ts index 60a06a81..4ebc7c12 100644 --- a/web/document_editor.ts +++ b/web/document_editor.ts @@ -14,7 +14,8 @@ export class DocumentEditor { readonly parent: HTMLElement, readonly client: Client, readonly saveMethod: (path: string, content: Uint8Array) => void, - ) {} + ) { + } async init(extension: string) { this.extension = extension; @@ -62,32 +63,6 @@ export class DocumentEditor { this.iframe.remove(); } - private async waitForSave() { - if (this.savePromise) { - try { - await Promise.race([ - this.savePromise.promise, - timeout(2500), - ]); - } catch { - this.savePromise.resolve(); - this.savePromise = null; - - console.log( - "Unable to save content of document editor in 2.5s. Aborting save", - ); - } - } - } - - private sendMessage( - message: { type: string; internal?: boolean; data?: any }, - ) { - if (!this.iframe?.contentWindow) return; - message.internal ??= false; - this.iframe.contentWindow.postMessage(message); - } - sendPublicMessage(message: { type: string; data?: any }) { this.sendMessage(message); } @@ -148,6 +123,32 @@ export class DocumentEditor { }); } + private async waitForSave() { + if (this.savePromise) { + try { + await Promise.race([ + this.savePromise.promise, + timeout(2500), + ]); + } catch { + this.savePromise.resolve(); + this.savePromise = null; + + console.log( + "Unable to save content of document editor in 2.5s. Aborting save", + ); + } + } + } + + private sendMessage( + message: { type: string; internal?: boolean; data?: any }, + ) { + if (!this.iframe?.contentWindow) return; + message.internal ??= false; + this.iframe.contentWindow.postMessage(message); + } + private async messageHandler(event: any) { if (event.source !== this.iframe.contentWindow) return; const response = event.data; diff --git a/web/editor_state.ts b/web/editor_state.ts index f72bbdca..466cedfc 100644 --- a/web/editor_state.ts +++ b/web/editor_state.ts @@ -21,7 +21,7 @@ import { LanguageSupport, syntaxHighlighting, } from "@codemirror/language"; -import { EditorState } from "@codemirror/state"; +import { Compartment, EditorState, type Extension } from "@codemirror/state"; import { drawSelection, dropCursor, @@ -48,7 +48,6 @@ import type { TextChange } from "./change.ts"; import { postScriptPrefacePlugin } from "./cm_plugins/top_bottom_panels.ts"; import { languageFor } from "./languages.ts"; import { plugLinter } from "./cm_plugins/lint.ts"; -import { Compartment, type Extension } from "@codemirror/state"; import { extendedMarkdownLanguage } from "./markdown_parser/parser.ts"; import { safeRun } from "$lib/async.ts"; import { codeCopyPlugin } from "./cm_plugins/code_copy.ts"; diff --git a/web/editor_ui.tsx b/web/editor_ui.tsx index 4743b810..52995ff0 100644 --- a/web/editor_ui.tsx +++ b/web/editor_ui.tsx @@ -19,7 +19,6 @@ import type { FilterOption } from "@silverbulletmd/silverbullet/type/client"; export class MainUI { viewState: AppViewState = initialViewState; - viewDispatch: (action: Action) => void = () => {}; constructor(private client: Client) { // Make keyboard shortcuts work even when the editor is in read only mode or not focused @@ -69,6 +68,9 @@ export class MainUI { }); } + viewDispatch: (action: Action) => void = () => { + }; + ViewComponent() { const [viewState, dispatch] = useReducer(reducer, initialViewState); this.viewState = viewState; @@ -324,8 +326,9 @@ export class MainUI { icon: featherIcons.MoreVertical, description: "Open Menu", class: "expander", - callback: - () => {/* nothing to do, menu opens on hover/mobile click */}, + callback: () => { + /* nothing to do, menu opens on hover/mobile click */ + }, }] : [], // Custom action buttons diff --git a/web/hooks/command.ts b/web/hooks/command.ts index a16fa1f7..3787c8f4 100644 --- a/web/hooks/command.ts +++ b/web/hooks/command.ts @@ -9,6 +9,9 @@ export class CommandHook extends EventEmitter implements Hook { editorCommands = new Map(); system!: System; + throttledBuildAllCommands = throttle(() => { + this.buildAllCommands(); + }, 200); constructor( private readOnly: boolean, @@ -17,10 +20,6 @@ export class CommandHook extends EventEmitter super(); } - throttledBuildAllCommands = throttle(() => { - this.buildAllCommands(); - }, 200); - buildAllCommands() { this.editorCommands.clear(); for (const plug of this.system.loadedPlugs.values()) { diff --git a/web/hooks/document_editor.ts b/web/hooks/document_editor.ts index c723982e..ec8f45ca 100644 --- a/web/hooks/document_editor.ts +++ b/web/hooks/document_editor.ts @@ -9,7 +9,8 @@ export class DocumentEditorHook implements Hook { { extensions: string[]; callback: DocumentEditorCallback } >(); - constructor() {} + constructor() { + } collectAllDocumentEditors(system: System) { this.documentEditors.clear(); diff --git a/web/hooks/event.ts b/web/hooks/event.ts index 6fba046a..f2438b44 100644 --- a/web/hooks/event.ts +++ b/web/hooks/event.ts @@ -8,9 +8,9 @@ import type { EventHookT } from "$lib/manifest.ts"; // - plug:load (plugName: string) export class EventHook implements EventHookI { + public scriptEnvironment?: ScriptEnvironment; private system?: System; private localListeners: Map any)[]> = new Map(); - public scriptEnvironment?: ScriptEnvironment; addLocalListener(eventName: string, callback: (...args: any[]) => any) { if (!this.localListeners.has(eventName)) { diff --git a/web/hooks/plug_namespace.ts b/web/hooks/plug_namespace.ts index 84c913a2..0ae5f5db 100644 --- a/web/hooks/plug_namespace.ts +++ b/web/hooks/plug_namespace.ts @@ -14,7 +14,9 @@ type SpaceFunction = { export class PlugNamespaceHook implements Hook { spaceFunctions: SpaceFunction[] = []; - constructor() {} + + constructor() { + } apply(system: System): void { system.on({ diff --git a/web/hooks/slash_command.ts b/web/hooks/slash_command.ts index 982c51c8..0dd88fdd 100644 --- a/web/hooks/slash_command.ts +++ b/web/hooks/slash_command.ts @@ -24,14 +24,13 @@ const slashCommandRegexp = /([^\w:]|^)\/[\w#\-]*/; export class SlashCommandHook implements Hook { slashCommands: AppSlashCommand[] = []; - - constructor(private client: Client) { - } - throttledBuildAllCommands = throttle(() => { this.buildAllCommands(); }, 200); + constructor(private client: Client) { + } + buildAllCommands() { const clientSystem = this.client.clientSystem; const system = clientSystem.system; diff --git a/web/hooks/syscall.ts b/web/hooks/syscall.ts index 5b89a893..46d47a3b 100644 --- a/web/hooks/syscall.ts +++ b/web/hooks/syscall.ts @@ -1,7 +1,6 @@ import type { Hook, Manifest } from "$lib/plugos/types.ts"; -import type { System } from "$lib/plugos/system.ts"; +import type { SysCallMapping, System } from "$lib/plugos/system.ts"; import type { SyscallHookT } from "$lib/manifest.ts"; -import type { SysCallMapping } from "$lib/plugos/system.ts"; export class SyscallHook implements Hook { apply(system: System): void { diff --git a/web/index.html b/web/index.html index 3c99655b..ca7d2972 100644 --- a/web/index.html +++ b/web/index.html @@ -1,73 +1,74 @@ - - + + - - + + - + {{TITLE}} - - + + - + - - - - + + + + - -
-
+ +
+
-
-
- {{CONTENT}} +
+
+ {{CONTENT}} +
-
-
- +
+ diff --git a/web/languages.ts b/web/languages.ts index e6c0a665..67d24966 100644 --- a/web/languages.ts +++ b/web/languages.ts @@ -4,26 +4,40 @@ import { standardSQL as sqlLanguage, } from "@codemirror/legacy-modes/mode/sql?external=@codemirror/language&target=es2022"; import { rust as rustLanguage } from "@codemirror/legacy-modes/mode/rust?external=@codemirror/language&target=es2022"; -import { python as pythonLanguage } from "@codemirror/legacy-modes/mode/python?external=@codemirror/language&target=es2022"; -import { protobuf as protobufLanguage } from "@codemirror/legacy-modes/mode/protobuf?external=@codemirror/language&target=es2022"; +import { + python as pythonLanguage, +} from "@codemirror/legacy-modes/mode/python?external=@codemirror/language&target=es2022"; +import { + protobuf as protobufLanguage, +} from "@codemirror/legacy-modes/mode/protobuf?external=@codemirror/language&target=es2022"; import { shell as shellLanguage } from "@codemirror/legacy-modes/mode/shell?external=@codemirror/language&target=es2022"; import { swift as swiftLanguage } from "@codemirror/legacy-modes/mode/swift?external=@codemirror/language&target=es2022"; import { toml as tomlLanguage } from "@codemirror/legacy-modes/mode/toml?external=@codemirror/language&target=es2022"; import { xml as xmlLanguage } from "@codemirror/legacy-modes/mode/xml?external=@codemirror/language&target=es2022"; -import { json as jsonLanguage } from "@codemirror/legacy-modes/mode/javascript?external=@codemirror/language&target=es2022"; +import { + json as jsonLanguage, +} from "@codemirror/legacy-modes/mode/javascript?external=@codemirror/language&target=es2022"; import { r as rLanguage } from "@codemirror/legacy-modes/mode/r?external=@codemirror/language&target=es2022"; import { htmlLanguage } from "@codemirror/lang-html"; import { go as goLanguage } from "@codemirror/legacy-modes/mode/go?external=@codemirror/language&target=es2022"; import { diff as diffLanguage } from "@codemirror/legacy-modes/mode/diff?external=@codemirror/language&target=es2022"; -import { powerShell as powerShellLanguage } from "@codemirror/legacy-modes/mode/powershell?external=@codemirror/language&target=es2022"; +import { + powerShell as powerShellLanguage, +} from "@codemirror/legacy-modes/mode/powershell?external=@codemirror/language&target=es2022"; import { perl as perlLanguage } from "@codemirror/legacy-modes/mode/perl?external=@codemirror/language&target=es2022"; import { ruby as rubyLanguage } from "@codemirror/legacy-modes/mode/ruby?external=@codemirror/language&target=es2022"; import { tcl as tclLanguage } from "@codemirror/legacy-modes/mode/tcl?external=@codemirror/language&target=es2022"; -import { verilog as verilogLanguage } from "@codemirror/legacy-modes/mode/verilog?external=@codemirror/language&target=es2022"; +import { + verilog as verilogLanguage, +} from "@codemirror/legacy-modes/mode/verilog?external=@codemirror/language&target=es2022"; import { vhdl as vhdlLanguage } from "@codemirror/legacy-modes/mode/vhdl?external=@codemirror/language&target=es2022"; -import { dockerFile as dockerfileLanguage } from "@codemirror/legacy-modes/mode/dockerfile?external=@codemirror/language&target=es2022"; +import { + dockerFile as dockerfileLanguage, +} from "@codemirror/legacy-modes/mode/dockerfile?external=@codemirror/language&target=es2022"; import { cmake as cmakeLanguage } from "@codemirror/legacy-modes/mode/cmake?external=@codemirror/language&target=es2022"; -import { erlang as erlangLanguage } from "@codemirror/legacy-modes/mode/erlang?external=@codemirror/language&target=es2022"; +import { + erlang as erlangLanguage, +} from "@codemirror/legacy-modes/mode/erlang?external=@codemirror/language&target=es2022"; import { c as cLanguage, cpp as cppLanguage, @@ -42,7 +56,7 @@ import { } from "@codemirror/lang-javascript"; import { cssLanguage } from "@codemirror/lang-css"; import { nixLanguage } from "@replit/codemirror-lang-nix"; -import { luaLanguage } from "./space_lua/parse.ts"; +import { luaLanguage } from "../lib/space_lua/parse.ts"; const yamlStreamLanguage = StreamLanguage.define(yamlLanguage); diff --git a/web/manifest.json b/web/manifest.json index d7811553..12a73f6d 100644 --- a/web/manifest.json +++ b/web/manifest.json @@ -11,7 +11,9 @@ "capture_links": "new-client", "start_url": "/#boot", "display": "standalone", - "display_override": ["window-controls-overlay"], + "display_override": [ + "window-controls-overlay" + ], "scope": "/", "theme_color": "#e1e1e1", "description": "Markdown as a platform" diff --git a/web/markdown.ts b/web/markdown.ts index 6c9f00ff..9171b09d 100644 --- a/web/markdown.ts +++ b/web/markdown.ts @@ -10,10 +10,10 @@ import { validatePageName, } from "@silverbulletmd/silverbullet/lib/page_ref"; import { parseMarkdown } from "./markdown_parser/parser.ts"; -import type { LuaExpression } from "./space_lua/ast.ts"; -import { evalExpression } from "./space_lua/eval.ts"; -import type { LuaEnv, LuaStackFrame } from "./space_lua/runtime.ts"; -import { parseExpressionString } from "./space_lua/parse.ts"; +import type { LuaExpression } from "../lib/space_lua/ast.ts"; +import { evalExpression } from "../lib/space_lua/eval.ts"; +import type { LuaEnv, LuaStackFrame } from "../lib/space_lua/runtime.ts"; +import { parseExpressionString } from "../lib/space_lua/parse.ts"; import { renderExpressionResult } from "./markdown_util.ts"; import type { Client } from "../web/client.ts"; diff --git a/web/markdown_parser/parser.ts b/web/markdown_parser/parser.ts index 3a1e3ce9..2aec2bcb 100644 --- a/web/markdown_parser/parser.ts +++ b/web/markdown_parser/parser.ts @@ -8,10 +8,15 @@ import { Superscript, } from "@lezer/markdown"; import { markdown } from "@codemirror/lang-markdown"; -import { StreamLanguage } from "@codemirror/language"; +import { foldNodeProp, StreamLanguage } from "@codemirror/language"; import * as ct from "./customtags.ts"; import { NakedURLTag } from "./customtags.ts"; import { TaskList } from "./extended_task.ts"; +import { Table } from "./table_parser.ts"; +import { pWikiLinkRegex, tagRegex } from "../markdown_parser/constants.ts"; +import { parse } from "../markdown_parser/parse_tree.ts"; +import type { ParseTree } from "@silverbulletmd/silverbullet/lib/tree"; +import { luaLanguage } from "../../lib/space_lua/parse.ts"; const WikiLink: MarkdownConfig = { defineNodes: [ @@ -307,13 +312,6 @@ const TaskDeadline = regexParser({ tag: ct.TaskDeadlineTag, }); -import { Table } from "./table_parser.ts"; -import { foldNodeProp } from "@codemirror/language"; -import { pWikiLinkRegex, tagRegex } from "../markdown_parser/constants.ts"; -import { parse } from "../markdown_parser/parse_tree.ts"; -import type { ParseTree } from "@silverbulletmd/silverbullet/lib/tree"; -import { luaLanguage } from "../space_lua/parse.ts"; - // FrontMatter parser const yamlLang = StreamLanguage.define(yamlLanguage); diff --git a/web/markdown_util.ts b/web/markdown_util.ts index 1beb8ae2..ca8ed07b 100644 --- a/web/markdown_util.ts +++ b/web/markdown_util.ts @@ -1,4 +1,4 @@ -import { LuaTable, luaToString } from "./space_lua/runtime.ts"; +import { LuaTable, luaToString } from "../lib/space_lua/runtime.ts"; export function defaultTransformer(v: any): Promise { if (v === undefined) { diff --git a/web/reducer.ts b/web/reducer.ts index 2bf94a34..a9c37fc3 100644 --- a/web/reducer.ts +++ b/web/reducer.ts @@ -228,7 +228,8 @@ export default function reducer( showCommandPalette: false, showPageNavigator: false, showFilterBox: false, - filterBoxOnSelect: () => {}, + filterBoxOnSelect: () => { + }, filterBoxPlaceHolder: "", filterBoxOptions: [], filterBoxHelpText: "", diff --git a/web/shell_stream_client.ts b/web/shell_stream_client.ts index 8a258934..0501a554 100644 --- a/web/shell_stream_client.ts +++ b/web/shell_stream_client.ts @@ -99,74 +99,6 @@ export class ShellStreamClient { }); } - /** - * Set up WebSocket event handlers - * @private - */ - private setupEventHandlers(): void { - if (!this.ws) return; - - // Set up event handlers - this.ws.onopen = () => { - this.emitEvent({ type: "open", data: "Connected" }); - }; - - this.ws.onmessage = (event) => { - try { - const message = JSON.parse(event.data); - // Special handling for exit events - if (message.type === "exit") { - // Parse the exit code from the JSON string if needed - let exitData = message.data; - if (typeof exitData === "string" && typeof exitData === "string") { - try { - // Try to parse as JSON in case it's a stringified object - const parsedData = JSON.parse(exitData); - if ( - typeof parsedData === "object" && parsedData !== null && - "code" in parsedData - ) { - exitData = parsedData; - } - } catch (e) { - // If it's not valid JSON, just use it as is - console.error("Failed to parse exit data:", e); - } - } - - this.emitEvent({ - type: message.type, - data: exitData, - }); - } else { - // Regular event - this.emitEvent({ - type: message.type, - data: message.data, - }); - } - } catch (e) { - const error = e as Error; - this.emitEvent({ - type: "error", - data: `Error parsing message: ${error.message}`, - }); - } - }; - - this.ws.onclose = () => { - this.ws = null; - this.emitEvent({ type: "close", data: "Disconnected" }); - }; - - this.ws.onerror = (error) => { - this.emitEvent({ - type: "error", - data: `WebSocket error: ${error}`, - }); - }; - } - /** * Close the WebSocket connection */ @@ -240,6 +172,74 @@ export class ShellStreamClient { } } + /** + * Set up WebSocket event handlers + * @private + */ + private setupEventHandlers(): void { + if (!this.ws) return; + + // Set up event handlers + this.ws.onopen = () => { + this.emitEvent({ type: "open", data: "Connected" }); + }; + + this.ws.onmessage = (event) => { + try { + const message = JSON.parse(event.data); + // Special handling for exit events + if (message.type === "exit") { + // Parse the exit code from the JSON string if needed + let exitData = message.data; + if (typeof exitData === "string" && typeof exitData === "string") { + try { + // Try to parse as JSON in case it's a stringified object + const parsedData = JSON.parse(exitData); + if ( + typeof parsedData === "object" && parsedData !== null && + "code" in parsedData + ) { + exitData = parsedData; + } + } catch (e) { + // If it's not valid JSON, just use it as is + console.error("Failed to parse exit data:", e); + } + } + + this.emitEvent({ + type: message.type, + data: exitData, + }); + } else { + // Regular event + this.emitEvent({ + type: message.type, + data: message.data, + }); + } + } catch (e) { + const error = e as Error; + this.emitEvent({ + type: "error", + data: `Error parsing message: ${error.message}`, + }); + } + }; + + this.ws.onclose = () => { + this.ws = null; + this.emitEvent({ type: "close", data: "Disconnected" }); + }; + + this.ws.onerror = (error) => { + this.emitEvent({ + type: "error", + data: `WebSocket error: ${error}`, + }); + }; + } + /** * Emit an event to all registered handlers */ diff --git a/web/space_lua.ts b/web/space_lua.ts index 8b59bb5c..7257f46e 100644 --- a/web/space_lua.ts +++ b/web/space_lua.ts @@ -1,12 +1,19 @@ import type { System } from "../lib/plugos/system.ts"; import type { ScriptObject } from "../plugs/index/script.ts"; -import { LuaEnv, LuaRuntimeError, LuaStackFrame } from "./space_lua/runtime.ts"; -import { parse as parseLua, parseExpressionString } from "./space_lua/parse.ts"; -import { evalStatement } from "./space_lua/eval.ts"; +import { + LuaEnv, + LuaRuntimeError, + LuaStackFrame, +} from "../lib/space_lua/runtime.ts"; +import { + parse as parseLua, + parseExpressionString, +} from "../lib/space_lua/parse.ts"; +import { evalStatement } from "../lib/space_lua/eval.ts"; import { parseRef, type Ref } from "@silverbulletmd/silverbullet/lib/page_ref"; -import type { ASTCtx } from "./space_lua/ast.ts"; +import type { ASTCtx } from "../lib/space_lua/ast.ts"; import { buildLuaEnv } from "./space_lua_api.ts"; -import type { LuaCollectionQuery } from "./space_lua/query_collection.ts"; +import type { LuaCollectionQuery } from "../lib/space_lua/query_collection.ts"; export class SpaceLuaEnvironment { env: LuaEnv = new LuaEnv(); diff --git a/web/space_lua_api.ts b/web/space_lua_api.ts index ba0a9f1a..b3615590 100644 --- a/web/space_lua_api.ts +++ b/web/space_lua_api.ts @@ -1,4 +1,4 @@ -import { luaBuildStandardEnv } from "./space_lua/stdlib.ts"; +import { luaBuildStandardEnv } from "../lib/space_lua/stdlib.ts"; import { parseRef } from "@silverbulletmd/silverbullet/lib/page_ref"; import { LuaEnv, @@ -6,7 +6,7 @@ import { type LuaRuntimeError, LuaStackFrame, LuaTable, -} from "./space_lua/runtime.ts"; +} from "../lib/space_lua/runtime.ts"; import type { System } from "$lib/plugos/system.ts"; export function buildLuaEnv(system: System) { diff --git a/web/space_script.ts b/web/space_script.ts index b19abeac..cc0a846e 100644 --- a/web/space_script.ts +++ b/web/space_script.ts @@ -2,7 +2,7 @@ import type { ParseTree } from "../plug-api/lib/tree.ts"; import type { AppCommand, CommandDef, SlashCommand } from "$lib/command.ts"; import type { SlashCommandDef } from "$lib/manifest.ts"; import type { JSONSchemaType } from "ajv"; -import { jsToLuaValue, type LuaTable } from "./space_lua/runtime.ts"; +import { jsToLuaValue, type LuaTable } from "../lib/space_lua/runtime.ts"; type FunctionDef = { name: string; diff --git a/web/styles/top.scss b/web/styles/top.scss index 31f18422..18b60e27 100644 --- a/web/styles/top.scss +++ b/web/styles/top.scss @@ -33,7 +33,7 @@ font-size: 15px; z-index: 100; - >div { + > div { padding: 3px; margin-bottom: 3px; border-radius: 5px; @@ -98,7 +98,6 @@ } @media only screen and (max-width: 600px) { - /* Style the menu as a vertical fly-out */ #sb-top .cm-scroller:has(.hamburger) { position: relative; @@ -116,9 +115,11 @@ border-radius: 5px; border: 1px solid #444; width: 2.8rem; - background: color-mix(in srgb, - var(--top-background-color) 90%, - transparent); + background: color-mix( + in srgb, + var(--top-background-color) 90%, + transparent + ); backdrop-filter: blur(20px); overflow: hidden; flex-direction: column; @@ -184,4 +185,4 @@ } } } -} \ No newline at end of file +} diff --git a/web/sync_service.ts b/web/sync_service.ts index 33dc9a6e..9170930f 100644 --- a/web/sync_service.ts +++ b/web/sync_service.ts @@ -42,6 +42,7 @@ export class SyncService { // If this is set to anything other than undefined, a file is currently saving savingTimeout: number | undefined; enabled = true; + filesScheduledForSync = new Set(); constructor( readonly localSpacePrimitives: SpacePrimitives, @@ -213,8 +214,6 @@ export class SyncService { } } - filesScheduledForSync = new Set(); - async scheduleFileSync(path: string): Promise { if (this.filesScheduledForSync.has(path)) { // Already scheduled, no need to duplicate diff --git a/web/syscalls/command.ts b/web/syscalls/command.ts index 837f54de..81d3a0b4 100644 --- a/web/syscalls/command.ts +++ b/web/syscalls/command.ts @@ -7,7 +7,7 @@ import { luaCall, LuaStackFrame, luaValueToJS, -} from "../space_lua/runtime.ts"; +} from "../../lib/space_lua/runtime.ts"; import type { ClientSystem } from "../client_system.ts"; export type CallbackCommandDef = CommandDef & { diff --git a/web/syscalls/datastore.ts b/web/syscalls/datastore.ts index d8e210bf..62871152 100644 --- a/web/syscalls/datastore.ts +++ b/web/syscalls/datastore.ts @@ -1,7 +1,7 @@ import { type LuaCollectionQuery, queryLua, -} from "../space_lua/query_collection.ts"; +} from "../../lib/space_lua/query_collection.ts"; import type { KV, KvKey } from "../../plug-api/types.ts"; import type { DataStore } from "../../lib/data/datastore.ts"; import type { SysCallMapping } from "../../lib/plugos/system.ts"; @@ -10,7 +10,7 @@ import { LuaEnv, LuaStackFrame, luaValueToJS, -} from "../space_lua/runtime.ts"; +} from "../../lib/space_lua/runtime.ts"; import type { KvQueryOptions } from "$lib/data/kv_primitives.ts"; import type { ClientSystem } from "../client_system.ts"; diff --git a/web/syscalls/event.ts b/web/syscalls/event.ts index 5bd1ce6d..a499d143 100644 --- a/web/syscalls/event.ts +++ b/web/syscalls/event.ts @@ -7,7 +7,7 @@ import { luaCall, LuaStackFrame, luaValueToJS, -} from "../space_lua/runtime.ts"; +} from "../../lib/space_lua/runtime.ts"; import type { ClientSystem } from "../client_system.ts"; export type CallbackEventListener = EventListenerDef & { diff --git a/web/syscalls/index.ts b/web/syscalls/index.ts index 5e1c1320..4c57e09d 100644 --- a/web/syscalls/index.ts +++ b/web/syscalls/index.ts @@ -2,12 +2,12 @@ import type { SysCallMapping } from "$lib/plugos/system.ts"; import type { LuaCollectionQuery, LuaQueryCollection, -} from "../space_lua/query_collection.ts"; +} from "../../lib/space_lua/query_collection.ts"; import { jsToLuaValue, type LuaEnv, type LuaStackFrame, -} from "../space_lua/runtime.ts"; +} from "../../lib/space_lua/runtime.ts"; import type { Client } from "../client.ts"; import type { TagDef } from "../space_script.ts"; diff --git a/web/syscalls/lua.ts b/web/syscalls/lua.ts index 6ac52ee1..b21cd549 100644 --- a/web/syscalls/lua.ts +++ b/web/syscalls/lua.ts @@ -1,14 +1,14 @@ import type { SysCallMapping } from "$lib/plugos/system.ts"; -import { evalExpression } from "../space_lua/eval.ts"; -import { parse, parseExpressionString } from "../space_lua/parse.ts"; +import { evalExpression } from "../../lib/space_lua/eval.ts"; +import { parse, parseExpressionString } from "../../lib/space_lua/parse.ts"; import { LuaStackFrame, luaToString, luaValueToJS, -} from "../space_lua/runtime.ts"; +} from "../../lib/space_lua/runtime.ts"; import { buildThreadLocalEnv } from "../space_lua_api.ts"; import { isSendable } from "$lib/plugos/util.ts"; -import type { LuaBlock, LuaExpression } from "../space_lua/ast.ts"; +import type { LuaBlock, LuaExpression } from "../../lib/space_lua/ast.ts"; import type { ClientSystem } from "../client_system.ts"; export function luaSyscalls(clientSystem: ClientSystem): SysCallMapping { diff --git a/web/syscalls/markdown.ts b/web/syscalls/markdown.ts index ecfd3817..c0e51d54 100644 --- a/web/syscalls/markdown.ts +++ b/web/syscalls/markdown.ts @@ -4,7 +4,7 @@ import { type ParseTree, renderToText } from "../../plug-api/lib/tree.ts"; import { extendedMarkdownLanguage } from "../markdown_parser/parser.ts"; import { expandMarkdown } from "../markdown.ts"; import type { Client } from "../client.ts"; -import { LuaStackFrame } from "../space_lua/runtime.ts"; +import { LuaStackFrame } from "../../lib/space_lua/runtime.ts"; export function markdownSyscalls(client: Client): SysCallMapping { return { diff --git a/web/type.ts b/web/type.ts index 143665b7..5d5f1ef2 100644 --- a/web/type.ts +++ b/web/type.ts @@ -101,7 +101,8 @@ export const initialViewState: AppViewState = { showFilterBox: false, filterBoxHelpText: "", filterBoxLabel: "", - filterBoxOnSelect: () => {}, + filterBoxOnSelect: () => { + }, filterBoxOptions: [], filterBoxPlaceHolder: "",