* [Major] Introduce a new `Ref` and `Path` type
This introduces a new `Ref` and `Path` type, which try to be as lightweight and rigorous as possible. More description in the PR
* [Minor] Updated Syntax
* [Minor] Updated Type
* [Minor] Renamed file
* [Minor] Updated type
* [Minor] Updated Syntax
* [Major] Rename css classes for wiki links
Previously all the parts of a wiki link were tagged with `sb-wiki-link-page`. This didn't make a whole lot of sense. Also the tag `sb-wiki-link` was could never render, because of the way `HighlightStyle` works. Now every part of a wiki link will be tagged with `sb-wiki-link` (i.e. the link, the alias, the dimensions). Invalid and missing links will be styled separately in a later commit.
* [Minor] Adjust to new API
* [Minor] Small changes
* [Minor] Function and file renames
* [Major] Syscall adjusments
Change behavior of `getCurrentPath` to actually return what's now considered a `Path`. `getCurrentPage(Meta)` now also correctly returns for documents. We could consider a rename to get rid of the `page` association
* [Major] Rework `Client`
Mostly minor adjustments for the new API. Some major changes in `navigateWithinPage` and some unecessary checks removed now
* [Major] Errors on navigate
This now throws an error when the user tries to navigate to a link that is invalid
* [Major] Minor adjustments and remove some old bulk
This does the typicall adjustments to the new API. A thing to consider is that invalid links aren't indexed. Also removed old code about template and query stuff. And reused a regex instead of copy pasting it.
* [Minor] Cleanup editor ui
* [Minor] Adjust to new API
* [Minor] Adjust to new API and reduce ugly regex
* [Minor] New API
* [Minor] Rework link cm plugin
* [Major] Rework wiki links
A lot of code cleanup, reworked for new APIs and also introduces "invalid links"
* [Minor] Part of the editor ui rework
* [Major] Adjust to new API
This also removes the `looksLikePathWithExtension` check. I don't think it was necessary.
* [Minor] PArt of editor ui rewrite
* [Minor] Adjust to new API
* [Major] Adjust renderer to new API
Invalid links now link to `#`
* [Major] Rewrite navigator
This still needs major rework, currently in a "got it working" state
* [Minor] Adjust file name
* [Minor] Mistake during rebase
* [Minor] Navigate to end of header
* [Minor] Remove `tweakEditorDOM` and use proper CM extensions
* [Major] Heavily clean up the navigation code
* [Minor] Make `PageNavigator` closable again
* [Minor] Small fixes to reduce errors bubbling up
* [Minor] Show error message if navigate fails
* [Major] Remove legacy `navigate({ kind = ` syntax
This removes the old navigation syntax. It's still supported but the user will be warned then using it. I also added some validation to editor.navigate for user provided refs.
* [Minor] Make notifications errors
* [Minor] Don't fail if header isn't found
* [Minor] Typo
* [Major] Remove cleanPageRef
* [Minor] Small bug
* [Minor] Align behavior between render and editor
* [Major] Remove `PageRef`. Pretty sure it's obsolete
* [Major] Remove pos from header as it's an invalid ref
* [Minor] Align with original behavior
* [Minor] Get tests working again
* [Minor] check that indexPage is valid
* [Major] Rewrite `resolve.ts` file
* [Minor] More tests, more fixes
* [Minor] Don't just append .md this is weird
* [Minor] More edge cases
* [Minor] Version bumping hono somehow fixes the `/` case
* [Minor] Smaller TODOs
* [Major] We can be a littler looser on the restrictions
* [Major] Change header indexing, to index with pos
* [Minor] More fixes for headers
* [Minor] Rename for clarity
* [Minor] Change more regexes to named groups
* [Major] Inline content rewrite
* [Minor] Fix TODO
* [Minor] Bump index version
* [Major] New Space Primitive to check paths
* [Minor] Fix #1214
* [Minor] Fix #1169
* [Minor] Remove TODO
This check is probably not needed anymore. Unsure. May look into it later
* [Minor] small bug
* [Minor] Not a good rebase without me needing to fix it afterwards
* [Minor] Improve fallback for file upload
* [Minor] Some fixes on the for resolving
* [Minor] Removed doubled test case
* [Minor] Resolve #860
* [Minor] Turn flash notification into an error
* [Minor] Change handling of `.` paths
* [Minor] Show empty URL for index page
* [Minor] Push correct ref to history
* [Major] Capture mini editor events in capture phase
This fixes the issue were when pressing arrowup or arrowdown changed the cursor position inside the editor
* [Major] Add prefix to `FilterList`
This now fixes issues were if you had a prefixed page you con't properly search for it, because the search considered the prefix as part of the name
* [Minor] A little rigor for empty headers
* [Major] Fix #1404
* [Minor] `parseInt` returns `NaN` handle that
* [Minor] Use `fileName`
* [Minor] Fix comment
* [Minor] Fixes
* [Major] disallow `foo.png.md` and `/../`, etc. cases
* [Major] Change name of conflicted pages
* [Minor] Remove unecessary check
* [Minor] Remove some split code
* [Minor] Allow deletion of invalid files
* [Minor] Bug in the regex
* [Minor] Give some possibility of fixing incorrect names
* [Minor] Tiny bug with header completion
* [Major] Docs
* [Minor] Ignore errors for navigation inside of a page
This could be a problem when SB e.g. cached a scrollTop for a page, which, because e.g. the file is now deleted, doesn't exist anymore and thus CM throws an error, when trying to navigate
* Update conflicted copy regex in Maintenance.md
---------
Co-authored-by: Zef Hemel <zef@zef.me>
190 lines
5.5 KiB
TypeScript
190 lines
5.5 KiB
TypeScript
import { HttpServer } from "../server/http_server.ts";
|
|
|
|
// To ship silverbullet as a single binary, we're importing all assets (client and plugs) as JSON blobs
|
|
import clientAssetBundle from "../dist/client_asset_bundle.json" with {
|
|
type: "json",
|
|
};
|
|
import plugAssetBundle from "../dist/plug_asset_bundle.json" with {
|
|
type: "json",
|
|
};
|
|
|
|
import { AssetBundle, type AssetJson } from "../lib/asset_bundle/bundle.ts";
|
|
|
|
import { resolve } from "@std/path";
|
|
import { sleep } from "../lib/async.ts";
|
|
import { MemoryKvPrimitives } from "../lib/data/memory_kv_primitives.ts";
|
|
import { isValidName } from "@silverbulletmd/silverbullet/lib/ref";
|
|
|
|
export type AuthOptions = {
|
|
authToken?: string;
|
|
user: string;
|
|
pass: string;
|
|
lockoutTime: number;
|
|
lockoutLimit: number;
|
|
};
|
|
|
|
export async function serveCommand(
|
|
options: {
|
|
hostname?: string;
|
|
port?: number;
|
|
user?: string;
|
|
auth?: string;
|
|
},
|
|
folder?: string,
|
|
) {
|
|
const hostname = options.hostname || Deno.env.get("SB_HOSTNAME") ||
|
|
"127.0.0.1";
|
|
const port = options.port ||
|
|
(Deno.env.get("SB_PORT") && +Deno.env.get("SB_PORT")!) || 3000;
|
|
|
|
const readOnly = !!Deno.env.get("SB_READ_ONLY");
|
|
|
|
const indexPage = Deno.env.get("SB_INDEX_PAGE") || "index";
|
|
if (!isValidName(indexPage)) {
|
|
console.error(
|
|
"SB_INDEX_PAGE has to be a valid page name.",
|
|
);
|
|
Deno.exit(1);
|
|
}
|
|
|
|
folder = folder || Deno.env.get("SB_FOLDER");
|
|
if (!folder) {
|
|
console.error(
|
|
"No folder specified. Please pass a folder as an argument or set SB_FOLDER environment variable.",
|
|
);
|
|
Deno.exit(1);
|
|
}
|
|
|
|
// This database is only used to store some auth based temporary values
|
|
let dbFile = Deno.env.get("SB_KV_DB") || ".silverbullet.db.json";
|
|
dbFile = resolve(folder, dbFile);
|
|
|
|
console.info(
|
|
`Storing database in ${dbFile}.`,
|
|
);
|
|
|
|
// The only use case of this KV is to store auth-related stuff (keys, hashes)
|
|
const baseKvPrimitives = new MemoryKvPrimitives(dbFile, {
|
|
throttleMs: 100,
|
|
});
|
|
await baseKvPrimitives.init();
|
|
|
|
console.info("Starting SilverBullet binding to", `${hostname}:${port}`);
|
|
if (hostname === "127.0.0.1") {
|
|
console.info(
|
|
`SilverBullet will only be available locally, to allow outside connections, pass -L0.0.0.0 as a flag, and put a TLS terminator on top.`,
|
|
);
|
|
}
|
|
|
|
let hostUrlPrefix = Deno.env.get("SB_URL_PREFIX");
|
|
if (hostUrlPrefix) {
|
|
// Normalize the host URL prefix (if set)
|
|
if (!hostUrlPrefix.startsWith("/")) {
|
|
hostUrlPrefix = "/" + hostUrlPrefix;
|
|
}
|
|
if (hostUrlPrefix.endsWith("/")) {
|
|
hostUrlPrefix = hostUrlPrefix.replace(/\/*$/, "");
|
|
}
|
|
|
|
if (hostUrlPrefix !== "") {
|
|
console.log(`Host URL Prefix: ${hostUrlPrefix}`);
|
|
} else {
|
|
hostUrlPrefix = undefined;
|
|
}
|
|
}
|
|
|
|
// Built-in authentication setup
|
|
const userAuth = options.user || Deno.env.get("SB_USER");
|
|
let userCredentials: AuthOptions | undefined;
|
|
if (userAuth) {
|
|
const [user, pass] = userAuth.split(":");
|
|
userCredentials = {
|
|
user,
|
|
pass,
|
|
authToken: Deno.env.get("SB_AUTH_TOKEN"),
|
|
// Default lockout: 10 failed login attempts in 1 minute
|
|
lockoutLimit: Deno.env.get("SB_LOCKOUT_LIMIT")
|
|
? Number(Deno.env.get("SB_LOCKOUT_LIMIT"))
|
|
: 10,
|
|
lockoutTime: Deno.env.get("SB_LOCKOUT_TIME")
|
|
? Number(Deno.env.get("SB_LOCKOUT_TIME"))
|
|
: 60,
|
|
};
|
|
console.log(
|
|
`User authentication enabled for user "${user}" with lockout limit ${userCredentials.lockoutLimit} and lockout time ${userCredentials.lockoutTime}s`,
|
|
);
|
|
}
|
|
|
|
const shellBackend = Deno.env.get("SB_SHELL_BACKEND") || "local";
|
|
const spaceIgnore = Deno.env.get("SB_SPACE_IGNORE");
|
|
|
|
// All plug code bundled into a JSON blob
|
|
const plugAssets = new AssetBundle(plugAssetBundle as AssetJson);
|
|
// All client files bundled into a JSON blob
|
|
const clientAssets = new AssetBundle(clientAssetBundle as AssetJson);
|
|
|
|
// Manifest configuration
|
|
buildPWAManifest(hostUrlPrefix, clientAssets);
|
|
|
|
// Build the web server
|
|
const httpServer = new HttpServer(
|
|
{
|
|
hostname,
|
|
port,
|
|
hostUrlPrefix,
|
|
indexPage,
|
|
spaceIgnore,
|
|
auth: userCredentials,
|
|
readOnly,
|
|
shellBackend,
|
|
pagesPath: folder,
|
|
},
|
|
clientAssets,
|
|
plugAssets,
|
|
baseKvPrimitives,
|
|
);
|
|
|
|
// And kick it off
|
|
await httpServer.start();
|
|
|
|
// Wait in an infinite loop (to keep the HTTP server running, only cancelable via Ctrl+C or other signal)
|
|
while (true) {
|
|
await sleep(10000);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Overrides SilverBullet's manifest.json dynamically based on some (optional) environment variables
|
|
*/
|
|
function buildPWAManifest(
|
|
hostUrlPrefix: string | undefined,
|
|
clientAssets: AssetBundle,
|
|
) {
|
|
const manifestName = Deno.env.get("SB_NAME");
|
|
const manifestDescription = Deno.env.get("SB_DESCRIPTION");
|
|
|
|
// Parse the default manifest.json
|
|
const manifestData = JSON.parse(
|
|
clientAssets.readTextFileSync(".client/manifest.json"),
|
|
);
|
|
if (manifestName) {
|
|
manifestData.name = manifestData.short_name = manifestName;
|
|
}
|
|
if (manifestDescription) {
|
|
manifestData.description = manifestDescription;
|
|
}
|
|
if (hostUrlPrefix) {
|
|
for (const icon of manifestData.icons) {
|
|
if (icon.src) icon.src = hostUrlPrefix + icon.src;
|
|
}
|
|
manifestData.start_url = hostUrlPrefix + manifestData.start_url;
|
|
manifestData.scope = hostUrlPrefix + manifestData.scope;
|
|
}
|
|
// Write it back into the asset bundle to be served by the HTTP server
|
|
clientAssets.writeTextFileSync(
|
|
".client/manifest.json",
|
|
"application/json",
|
|
JSON.stringify(manifestData),
|
|
);
|
|
}
|