feat: init

This commit is contained in:
hh
2026-08-08 16:30:22 +02:00
commit 8a609da778
59 changed files with 10053 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
import { Component, TFile } from "obsidian";
import { mount } from "svelte";
import App from "../src/ui/app.svelte";
import { DEFAULT_SETTINGS } from "../src/settings";
import { BoardStore } from "../src/vault/store.svelte";
const BOARDS = ["personal", "work", "reading", "home"];
const FOLDER = "Boards";
async function main(): Promise<void> {
const files = new Map<string, string>();
await Promise.all(
BOARDS.map(async (name) => {
const response = await fetch(`/tests/fixtures/${name}.md`);
files.set(`${FOLDER}/${name}.md`, await response.text());
}),
);
const handles = new Map(
[...files.keys()].map((path) => [path, new TFile(path)]),
);
const app = {
vault: {
getMarkdownFiles: () => [...handles.values()],
getAbstractFileByPath: (path: string) => handles.get(path) ?? null,
cachedRead: async (file: TFile) => files.get(file.path) ?? "",
read: async (file: TFile) => files.get(file.path) ?? "",
process: async (file: TFile, fn: (data: string) => string) => {
const next = fn(files.get(file.path) ?? "");
files.set(file.path, next);
store.queueReload(file.path);
return next;
},
on: () => ({}),
},
workspace: { getLeaf: () => ({}), activeEditor: null },
plugins: { plugins: {} },
};
const settings = { ...DEFAULT_SETTINGS, boardsFolder: FOLDER };
const store = new BoardStore(app as never, settings, () => {});
await store.reloadAll();
mount(App, {
target: document.getElementById("app") as HTMLElement,
props: {
app: app as never,
store,
settings,
component: new Component() as never,
},
});
}
void main();
+36
View File
@@ -0,0 +1,36 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>beaver-calendar preview</title>
<link rel="stylesheet" href="/app.css" />
<link rel="stylesheet" href="/theme.css" />
<link rel="stylesheet" href="/styles.css" />
<style>
body.theme-dark,
body.theme-light {
--accent-h: 254;
--accent-s: 80%;
--accent-l: 68%;
--font-interface:
-apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto,
sans-serif;
}
body {
margin: 0;
height: 100vh;
overflow: hidden;
background: var(--background-primary);
}
#app {
height: 100vh;
}
</style>
</head>
<body class="theme-dark">
<div id="app"></div>
<script src="/preview/bundle.js"></script>
</body>
</html>
+118
View File
@@ -0,0 +1,118 @@
import {
createReadStream,
existsSync,
mkdtempSync,
readdirSync,
statSync,
writeFileSync,
} from "node:fs";
import { createServer } from "node:http";
import { homedir, tmpdir } from "node:os";
import { extname, join, normalize } from "node:path";
import { extractFile } from "@electron/asar";
import esbuild from "esbuild";
import sveltePlugin from "esbuild-svelte";
const root = new URL("..", import.meta.url).pathname;
const port = Number(process.env.PORT ?? 4173);
const ASAR_CANDIDATES = [
process.env.OBSIDIAN_ASAR,
"/Applications/Obsidian.app/Contents/Resources/obsidian.asar",
join(homedir(), "Applications/Obsidian.app/Contents/Resources/obsidian.asar"),
"/opt/Obsidian/resources/obsidian.asar",
"/usr/lib/obsidian/resources/obsidian.asar",
].filter(Boolean);
function findAppCss() {
for (const candidate of ASAR_CANDIDATES) {
if (!existsSync(candidate)) continue;
try {
const path = join(mkdtempSync(join(tmpdir(), "bcal-app-")), "app.css");
writeFileSync(path, extractFile(candidate, "app.css"));
return path;
} catch {
/* try the next candidate */
}
}
console.warn(
"preview: could not read app.css from an Obsidian install. Set " +
"OBSIDIAN_ASAR to point at obsidian.asar. Without it, button and input " +
"chrome will not match the real app.",
);
return null;
}
function findTheme() {
if (process.env.THEME) return process.env.THEME;
const vault = process.env.VAULT;
if (!vault) return null;
const themes = join(vault, ".obsidian/themes");
if (!existsSync(themes)) return null;
for (const name of readdirSync(themes)) {
const css = join(themes, name, "theme.css");
if (existsSync(css)) return css;
}
return null;
}
const appCss = findAppCss();
const theme = findTheme();
if (!theme) {
console.warn(
"preview: no community theme loaded. Set VAULT to a vault path, or " +
"THEME to a theme.css, to preview against one.",
);
}
const ctx = await esbuild.context({
entryPoints: [join(root, "preview/entry.ts")],
bundle: true,
format: "iife",
target: "es2022",
outfile: join(root, "preview/bundle.js"),
logLevel: "info",
sourcemap: "inline",
conditions: ["svelte", "browser"],
mainFields: ["svelte", "browser", "module", "main"],
alias: { obsidian: join(root, "tests/obsidian-stub.ts") },
plugins: [sveltePlugin({ compilerOptions: { css: "injected", runes: true } })],
});
await ctx.rebuild();
await ctx.watch();
const TYPES = {
".css": "text/css; charset=utf-8",
".html": "text/html; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".md": "text/plain; charset=utf-8",
};
createServer((request, response) => {
const url = decodeURIComponent((request.url ?? "/").split("?")[0]);
const external = { "/app.css": appCss, "/theme.css": theme };
if (url in external) {
const file = external[url];
if (!file) {
response.writeHead(200, { "content-type": TYPES[".css"] }).end("");
return;
}
response.writeHead(200, { "content-type": TYPES[".css"], "cache-control": "no-store" });
createReadStream(file).pipe(response);
return;
}
const file = join(root, normalize(url === "/" ? "/preview/index.html" : url));
if (!file.startsWith(root) || !existsSync(file) || statSync(file).isDirectory()) {
response.writeHead(404).end("not found");
return;
}
response.writeHead(200, {
"content-type": TYPES[extname(file)] ?? "application/octet-stream",
"cache-control": "no-store",
});
createReadStream(file).pipe(response);
}).listen(port, () => {
console.log(`preview: http://localhost:${port}/`);
});