Libraries have been rethought, see associated documentation.
pull/905/head
Zef Hemel 2024-07-11 20:36:26 +02:00 committed by GitHub
parent 776206db90
commit 25d14031c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 347 additions and 160 deletions

View File

@ -1,19 +1,20 @@
export const SETTINGS_TEMPLATE = `---
tags: meta
---
This page contains settings for configuring SilverBullet and its plugs. A list of built-in settings [[!silverbullet.md/SETTINGS|can be found here]].
export const SETTINGS_TEMPLATE = `#meta
This page contains settings for configuring SilverBullet. A list of built-in settings [[!silverbullet.md/SETTINGS|can be found here]].
To update \`libraries\` specified here: {[Libraries: Update]}
\`\`\`yaml
indexPage: index
libraries:
- source: "[[!silverbullet.md/Library/Core/*]]"
\`\`\`
`;
export const INDEX_TEMPLATE =
`This is the index page of your fresh SilverBullet space. It is the default page that is loaded when you open a space. In addition, there is also a [[SETTINGS]] page that contains settings for configuring SilverBullet.
For your convenience we're including an on-boarding live template below. Enjoy!
For your convenience we're embedding some on-boarding info below. Feel free to delete it once you're done reading it.
\`\`\`include
raw: "[[!silverbullet.md/Getting Started]]"
\`\`\`
![[!silverbullet.md/Getting Started]]
`;

View File

@ -4,7 +4,7 @@ import {
FileMeta,
PageMeta,
} from "$sb/types.ts";
import { cacheFileListing } from "../federation/federation.ts";
import { listFilesCached } from "../federation/federation.ts";
import { queryObjects } from "../index/plug_api.ts";
import { folderName } from "$sb/lib/resolve.ts";
@ -39,12 +39,15 @@ export async function pageComplete(completeEvent: CompleteEvent) {
node.startsWith("FencedCode:template")
)
) {
// Include both pages and templates in page completion in ```include and ```template blocks
// Include both pages and meta in page completion in ```include and ```template blocks
allPages = await queryObjects<PageMeta>("page", {}, 5);
} else {
// Otherwise, just complete non-template pages
// Otherwise, just complete non-meta pages
allPages = await queryObjects<PageMeta>("page", {
filter: ["!=", ["attr", "tags"], ["string", "template"]],
filter: ["and", ["!=", ["attr", "tags"], ["string", "template"]], ["!=", [
"attr",
"tags",
], ["string", "meta"]]],
}, 5);
// and attachments
allPages = allPages.concat(
@ -64,7 +67,7 @@ export async function pageComplete(completeEvent: CompleteEvent) {
// Yep
const domain = prefix.split("/")[0];
// Cached listing
const federationPages = (await cacheFileListing(domain)).filter((fm) =>
const federationPages = (await listFilesCached(domain)).filter((fm) =>
fm.name.endsWith(".md")
).map(fileMetaToPageMeta);
if (federationPages.length > 0) {

View File

@ -29,9 +29,12 @@ functions:
pattern: "!.+"
operation: getFileMeta
# Library management
importLibraryCommand:
path: library.ts:importLibraryCommand
# Library management commands
updateLibrariesCommand:
path: library.ts:updateLibrariesCommand
command:
name: "Library: Import"
name: "Libraries: Update"
requireMode: rw
updateLibraries:
path: library.ts:updateLibraries
env: server

View File

@ -3,6 +3,7 @@ import { federatedPathToUrl } from "$sb/lib/resolve.ts";
import { readFederationConfigs } from "./config.ts";
import { datastore } from "$sb/syscalls.ts";
import type { FileMeta } from "../../plug-api/types.ts";
import { wildcardPathToRegex } from "./util.ts";
async function responseToFileMeta(
r: Response,
@ -31,7 +32,7 @@ async function responseToFileMeta(
}
const fileListingPrefixCacheKey = `federationListCache`;
const listingCacheTimeout = 1000 * 30;
const listingCacheTimeout = 1000 * 5;
const listingFetchTimeout = 2000;
type FileListingCacheEntry = {
@ -44,7 +45,7 @@ export async function listFiles(): Promise<FileMeta[]> {
// Fetch them all in parallel
try {
await Promise.all((await readFederationConfigs()).map(async (config) => {
const items = await cacheFileListing(config.uri);
const items = await listFilesCached(config.uri);
fileMetas = fileMetas.concat(items);
}));
@ -56,63 +57,78 @@ export async function listFiles(): Promise<FileMeta[]> {
}
}
export async function cacheFileListing(uri: string): Promise<FileMeta[]> {
export async function listFilesCached(
uri: string,
supportWildcards = false,
): Promise<FileMeta[]> {
const uriParts = uri.split("/");
const rootUri = uriParts[0];
const prefix = uriParts.slice(1).join("/");
console.log(
"Fetching listing from federated",
rootUri,
"with prefix",
prefix,
);
const cachedListing = await datastore.get(
[fileListingPrefixCacheKey, uri],
[fileListingPrefixCacheKey, rootUri],
) as FileListingCacheEntry;
let items: FileMeta[] = [];
if (
cachedListing &&
cachedListing.lastUpdated > Date.now() - listingCacheTimeout
) {
// console.info("Using cached listing", cachedListing);
return cachedListing.items;
}
console.log("Fetching listing from federated", uri);
const uriParts = uri.split("/");
const rootUri = uriParts[0];
const prefix = uriParts.slice(1).join("/");
const indexUrl = `${federatedPathToUrl(rootUri)}/index.json`;
try {
const fetchController = new AbortController();
const timeout = setTimeout(
() => fetchController.abort(),
listingFetchTimeout,
);
console.info("Using cached listing", cachedListing.items.length);
items = cachedListing.items;
} else {
const indexUrl = `${federatedPathToUrl(rootUri)}/index.json`;
try {
const fetchController = new AbortController();
const timeout = setTimeout(
() => fetchController.abort(),
listingFetchTimeout,
);
const r = await nativeFetch(indexUrl, {
method: "GET",
headers: {
"X-Sync-Mode": "true",
"Cache-Control": "no-cache",
},
signal: fetchController.signal,
});
clearTimeout(timeout);
const r = await nativeFetch(indexUrl, {
method: "GET",
headers: {
"X-Sync-Mode": "true",
"Cache-Control": "no-cache",
},
signal: fetchController.signal,
});
clearTimeout(timeout);
if (r.status !== 200) {
throw new Error(`Got status ${r.status}`);
}
const jsonResult = await r.json();
const items: FileMeta[] = jsonResult.filter((meta: FileMeta) =>
meta.name.startsWith(prefix)
).map((meta: FileMeta) => ({
...meta,
perm: "ro",
name: `${rootUri}/${meta.name}`,
}));
await datastore.set([fileListingPrefixCacheKey, uri], {
items,
lastUpdated: Date.now(),
} as FileListingCacheEntry);
return items;
} catch (e: any) {
console.error("Failed to process", indexUrl, e);
if (cachedListing) {
console.info("Using cached listing");
return cachedListing.items;
if (r.status !== 200) {
throw new Error(`Got status ${r.status}`);
}
const jsonResult = await r.json();
// Transform them a little bit
items = jsonResult.map((meta: FileMeta) => ({
...meta,
perm: "ro",
name: `${rootUri}/${meta.name}`,
}));
// Cache the entire listing
await datastore.set([fileListingPrefixCacheKey, rootUri], {
items,
lastUpdated: Date.now(),
} as FileListingCacheEntry);
} catch (e: any) {
console.error("Failed to process", indexUrl, e);
if (cachedListing) {
console.info("Using cached listing");
return cachedListing.items;
}
}
}
return [];
// And then filter based on prefix before returning
if (!supportWildcards) {
return items.filter((meta: FileMeta) => meta.name.startsWith(uri));
} else {
const prefixRegex = wildcardPathToRegex(uri);
return items.filter((meta) => prefixRegex.test(meta.name));
}
}
export async function readFile(

View File

@ -1,57 +1,116 @@
import { editor, space } from "$sb/syscalls.ts";
import { cacheFileListing, readFile } from "./federation.ts";
import { readSetting } from "$sb/lib/settings_page.ts";
import { listFilesCached, readFile } from "./federation.ts";
import { parsePageRef } from "$sb/lib/page_ref.ts";
import { invokeFunction } from "$sb/syscalls/system.ts";
import { federatedPathToLocalPath, wildcardPathToRegex } from "./util.ts";
import { confirm } from "$sb/syscalls/editor.ts";
export async function importLibraryCommand(_def: any, uri?: string) {
if (!uri) {
uri = await editor.prompt("Import library (federation URL):");
}
if (!uri) {
return;
}
uri = uri.trim();
if (!uri.startsWith("!")) {
uri = `!${uri}`;
}
const allTemplates = (await cacheFileListing(uri)).filter((f) =>
f.name.endsWith(".md")
);
type LibraryDef = {
source: string;
exclude?: string[];
};
export async function updateLibrariesCommand() {
if (
!await editor.confirm(
`You are about to import ${allTemplates.length} templates, want to do this?`,
await confirm(
"Are you sure you want to update all libraries (as specified in SETTINGS)?",
)
) {
return;
await editor.flashNotification("Updating all libraries...");
const updateStats: UpdateStats = await invokeFunction(
"federation.updateLibraries",
);
await editor.reloadSettingsAndCommands();
await editor.flashNotification(
`Updated ${updateStats.libraries} libraries containing a total of ${updateStats.items} items.`,
);
}
for (const template of allTemplates) {
// Clean up file path
let pageName = template.name.replace(/\.md$/, "");
// Remove the federation part
const pieces = pageName.split("/");
pageName = pieces.slice(1).join("/");
}
// Fetch the file
const buf = (await readFile(template.name)).data;
type UpdateStats = {
libraries: number;
items: number;
};
try {
// Check if it already exists
await space.getPageMeta(pageName);
// Run on the server for efficiency and CORS avoidance
export async function updateLibraries(): Promise<UpdateStats> {
const updateStats: UpdateStats = { libraries: 0, items: 0 };
const libraries = (await readSetting("libraries", [])) as LibraryDef[];
for (const lib of libraries) {
if (!lib.source) {
console.warn("Library source not set, skipping", lib);
continue;
}
const pageUri = parsePageRef(lib.source).page;
if (
!await editor.confirm(
`Page ${pageName} already exists, are you sure you want to override it?`,
)
) {
continue;
}
} catch {
// Expected
if (!pageUri.startsWith("!")) {
console.warn(
"Library source must be a federated page, skipping",
pageUri,
);
continue;
}
// Write to local space
await space.writePage(pageName, new TextDecoder().decode(buf));
console.log("Now updating library", pageUri);
await editor.flashNotification(`Imported ${pageName}`);
const localLibraryPath = federatedPathToLocalPath(pageUri);
// Sanity check the `source` pattern to avoid disaster (like wiping out the whole space)
if (!/^Library\/.+/.test(localLibraryPath)) {
console.warn(
"Skipping library",
pageUri,
"as it does not start with Library/",
);
continue;
}
// Fetch new list of pages
let newPages = await listFilesCached(pageUri, true);
console.log("All pages", newPages.length);
if (lib.exclude) {
for (const exclude of lib.exclude) {
const excludeUri = parsePageRef(exclude).page;
const excludeRegex = wildcardPathToRegex(excludeUri + ".md");
newPages = newPages.filter((p) => {
if (excludeRegex.test(p.name)) {
console.info("Excluding", p.name);
return false;
}
return true;
});
}
}
// Compile existing page list in local space (to be removed)
const localPages = await space.listPages();
const localSourceRegex = wildcardPathToRegex(localLibraryPath);
// Remove pages that match the source pattern, but in their "local" form
const pagesToRemove = localPages.filter((p) =>
localSourceRegex.test(p.name)
);
console.log("Pages to remove", pagesToRemove.length);
for (const page of pagesToRemove) {
console.info("Deleting", page.name);
await space.deletePage(page.name);
}
// Import the new pages
for (const page of newPages) {
console.info("Importing", page.name);
// Fetch the file
const buf = (await readFile(page.name)).data;
// Write to local space
await space.writeFile(federatedPathToLocalPath(page.name), buf);
updateStats.items++;
}
updateStats.libraries++;
console.log("Done with library", pageUri);
}
await editor.reloadSettingsAndCommands();
await editor.flashNotification("Import complete!");
return updateStats;
}

View File

@ -0,0 +1,20 @@
import { assert, assertEquals } from "$std/testing/asserts.ts";
import { federatedPathToLocalPath, wildcardPathToRegex } from "./util.ts";
Deno.test("Test wildcardPathToRegex", () => {
assert(wildcardPathToRegex("test").test("test"));
assert(wildcardPathToRegex("test").test("test.md"));
assert(wildcardPathToRegex("test*").test("test"));
assert(wildcardPathToRegex("test/*").test("test/bla"));
assert(wildcardPathToRegex("test/*").test("test/bla.md"));
assert(wildcardPathToRegex("test/*").test("test/bla/bla"));
assert(!wildcardPathToRegex("test/*").test("tests/bla/bla"));
});
Deno.test("Test federatedPathToLocalPath", () => {
assertEquals(federatedPathToLocalPath("!silverbullet.md"), "");
assertEquals(
federatedPathToLocalPath("!silverbullet.md/Library/Core/test"),
"Library/Core/test",
);
});

14
plugs/federation/util.ts Normal file
View File

@ -0,0 +1,14 @@
export function wildcardPathToRegex(pattern: string): RegExp {
// Escape special characters in the pattern except for the wildcard "*"
const escapedPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
// Replace the wildcard "*" with ".*" to match any character sequence
const regexPattern = escapedPattern.replace(/\*/g, ".*");
// Create a new regular expression with the converted pattern
return new RegExp(`^${regexPattern}(\\.md)?$`);
}
export function federatedPathToLocalPath(path: string): string {
return path.split("/").slice(1).join("/");
}

View File

@ -117,7 +117,7 @@ export class ServerSystem extends CommonSystem {
this.eventHook.addLocalListener(
"file:spaceSnapshotted",
(snapshot: Record<string, any>) => {
console.log("Space snapshot updated");
// console.log("Space snapshot updated");
return this.ds.set(["$spaceCache"], snapshot);
},
);

View File

@ -9,6 +9,7 @@ type LinkOptions = {
href?: string;
title: string;
cssClass: string;
from: number;
callback: (e: MouseEvent) => void;
};
export class LinkWidget extends WidgetType {
@ -56,6 +57,7 @@ export class LinkWidget extends WidgetType {
eq(other: WidgetType): boolean {
return other instanceof LinkWidget &&
this.options.from === other.options.from &&
this.options.text === other.options.text &&
this.options.href === other.options.href &&
this.options.title === other.options.title;

View File

@ -83,6 +83,7 @@ export function cleanWikiLinkPlugin(client: Client) {
cssClass: fileExists
? "sb-wiki-link-page"
: "sb-wiki-link-page-missing",
from,
callback: (e) => {
if (e.altKey) {
// Move cursor into the link

View File

@ -7,21 +7,22 @@ release.
_These features are not yet properly released, you need to use [the edge builds](https://community.silverbullet.md/t/living-on-the-edge-builds/27) to try them._
* The old **Template Picker** has now been rebranded to [[Meta Picker]] and surfaces pages in your space tagged as `#template` or `#meta`. Read more about this in [[Meta Pages]].
* [[Transclusion]] has now been implemented, allowing inline embeddings of other pages as well as images (by onespaceman) using the convenient `![[link]]` syntax.
* [[Transclusions]] has now been implemented, allowing inline embedding of other pages as well as images (by onespaceman) using the convenient `![[link]]` syntax.
* [[Libraries]] management has been rethought. You can now decoratively specify them in [[SETTINGS]] and keep them up to date with the {[Libraries: Update]} command.
* For new spaces, the default [[SETTINGS]] page is now tagged with `#meta`, which means it will only appear in the [[Meta Picker]]. There is also a new {[Navigate: Open SETTINGS]} command (bound to `Ctrl-,` and `Cmd-,`).
* Attachments are now indexed, and smartly moved when pages are renamed (by onespaceman)
* Images can now be resized: [[Attachments#Embedding]] (initial work done by [Florent](https://github.com/silverbulletmd/silverbullet/pull/833), later adapted by onespaceman)
* To make pure reading and browsing on touch devices a nicer experience, there is now a new **edit toggle** (top right). When _disabled_, you switch to _reader mode_ which makes sure your software keyboard doesnt block the screen when navigating your space. This button is only visible on mobile devices (no physical keyboard attached) only. Can be disabled via the `hideEditButton` [[SETTINGS]] (and is disabled on this website, thats why you dont see it).
* Super^script^ and Sub~script~ are now supported (by [MrMugame](https://github.com/silverbulletmd/silverbullet/pull/879))
* Added a {[Delete Line]} command (by [pihentagy](https://github.com/silverbulletmd/silverbullet/pull/866))
* The `#boot` PWA loading the last opened page feature is back (put `#boot` at the end of your SilverBullet URL to auto load the last opened page)
* Improved selection behavior (by [MrMugame](https://github.com/silverbulletmd/silverbullet/pull/904))
* Hide `\` escapes in [[Live Preview]] (by [MrMugame](https://github.com/silverbulletmd/silverbullet/pull/901))
* Added Erlang [[Markdown/Syntax Highlighting]]
* Dates (formatted as e.g. `2023-07-01` or `2023-07-01 23:33`) in [[Frontmatter]] are now converted into strings (rather than empty objects)
* **Breaking change:** The legacy handlebars template syntax that supported `{{escapeRegex "something"}}` function calls (rather than `{{escapeRegex("something")}}`) has now been removed.
* `task` and `item` objects now have an additional `text` attribute that contains the full text of the item and/or task, with any [[Attributes]] and [[Tags]] intact (whereas they are removed from `name`)
* The `#boot` PWA loading the last opened page feature is back (put `#boot` at the end of your SilverBullet URL to auto load the last opened page)
* Numerous other bug fixes (thanks MrMugame and onespaceman)
---
## 0.7.7

View File

@ -1,28 +1,25 @@
Federation enables _browsing_, and _synchronizing_ (parts of) spaces _outside_ the users space into your SilverBullet client.
Federation enables _browsing_ content from spaces _outside_ the users space, specified by quasi-URLs in the shape of `!domain.tld/path`. An example would be: [[!silverbullet.md/CHANGELOG]].
This enables a few things:
* **Linking and browsing** to other publicly hosted SilverBullet spaces (or websites adhering to its [[API]]). For instance the [[!silverbullet.md/CHANGELOG|SilverBullet CHANGELOG]] without leaving the comfort of your own SilverBullet client.
* **Reusing** content from externally hosted sources, such as:
* [[Libraries]] synchronization. By federating with `silverbullet.md/Library/Core`, you will get you access to the templates hosted there without copying ({[Library: Import]}ing) them and automatically pull in the latest versions.
* _Data_: such as tasks, item, data hosted elsewhere that you want to query from your own space.
* **Browsing** other publicly hosted SilverBullet spaces (or websites adhering to its [[API]]) within the comfort of your own SilverBullet client. One use case of this is [[Transclusions|transcluding]] the [[Getting Started]] page in the users automatically generated index page when setting up a fresh space.
* **Referencing** other spaces for other purposes, which is leveraged in [[Libraries]].
**Note:** Federation does not support authentication yet, so all federated spaces need to be unauthenticated and will be _read-only_.
# How it works
Effectively, any page [[Links|link]] starting with `!` is rewritten as follows:
## Browsing
Browsing other publicly hosted spaces is as simple as navigating to a page starting with `!` such as [[!silverbullet.md/CHANGELOG]].
* Replace the initial `!` with `https://`
* Append `.md` at the end
## Federating
To synchronize federated content into your client, you need to list these URIs in your [[SETTINGS]] under the `federate` key. For instance:
The resulting URL is then fetched and displayed in the editor in read-only mode. This means that you can navigate to _any markdown_ file on the (public) Internet.
```yaml
federate:
- uri: silverbullet.md/Library/Core/
```
For example: `https://raw.githubusercontent.com/silverbulletmd/silverbullet/main/README.md`
Can be written to federation syntax as follows: `!raw.githubusercontent.com/silverbulletmd/silverbullet/main/README`
And used as a link: [[!raw.githubusercontent.com/silverbulletmd/silverbullet/main/README]]
This will synchronize all content under `!silverbullet.md` with a `Library/Core/` prefix (so all templates hosted there) locally.
If the target server supports the SilverBullet [[API]] (specifically its `/index.json` endpoint), page completion will be provided as well.
Currently, content can only be synchronized in read-only mode, so you can not edit the synchronized files. This will likely change in the future.
Upon fetching of the page content, a best effort attempt will be made to rewrite any local page links in the page to the appropriate federated paths.
## Hosting
Tooling to make hosting public spaces is still a work in progress.
Tooling to make hosting public spaces easier is still a work in progress. The way it is enabled on `silverbullet.md` is by running a dedicated SilverBullet instance in read-only mode (see [[Install/Configuration#Run mode]]).

View File

@ -3,40 +3,42 @@ Welcome to the wondrous world of SilverBullet. A world that once you discover an
_One of us!_
Out of the box SilverBullet is fairly minimal in terms of functionality. To give you a good “first run” experience, we recommend you start by importing the [[Library/Core]] library into your space. This will give you a couple of useful pages, slash commands, page templates and widgets such as [[Table of Contents]], [[Linked Mentions]] and [[Linked Tasks]] to start of with. If you have no idea what those are — dont worry about it. Just keep going, youll get the hang of things.
Out of the box SilverBullet is fairly minimal in terms of functionality. To give you a good “first run” experience, we have preconfigured the [[Library/Core]] [[Libraries|library]] for you in your {[Navigate: Open SETTINGS|settings]}. All need to do is import it by pushing this button:
Just push this button: {[Library: Import|Import Core Library]("!silverbullet.md/Library/Core/")}
{[Libraries: Update]}
You know you want to.
Just push that button. You know you want to.
Just do it.
You can learn what you actually did later when you feel comfortable diving into [[Libraries]].
# Next steps
Now that you have some basics stuff in place, its time to start playing a bit.
Now that you have some basics stuff in place, its time to start playing around a little bit.
* Click on the page picker (book icon) icon at the top right, or hit `Cmd-k` (Mac) or `Ctrl-k` (Linux and Windows) to open the [[Page Picker]].
* Type the name of a non-existent page to create it.
* Folders are implicitly created by putting slashes (`/`) in the name (even on Windows), e.g. `My Folder/My Page`. Dont worry about folders existing, well automatically create them if they dont.
* Click on the terminal icon (top right), or hit `Cmd-/` (Mac) or `Ctrl-/` (Linux and Windows), or tap the screen with 3 fingers at the same time (on mobile) to open the [[Command Palette]]. From here you can run various useful and perhaps less useful [[Commands]]. The {[Stats: Show]} one is a safe one to try.
* Select some text and hit `Alt-m` to ==highlight== it, or `Cmd-b` (Mac) or `Ctrl-b` (Windows/Linux) to make it **bold**, or `Cmd-i` (Mac) or `Ctrl-i` (Windows/Linux) to make it _italic_.
* Click a link somewhere on this page to navigate there. When you link to a new page it will initially show up in red (to indicate it does not yet exist), but once you click it — you will create the page automatically (only for real when you actually enter some text).
* Select some text and hit `Cmd-b` (Mac) or `Ctrl-b` (Windows/Linux) to make it **bold**, or `Cmd-i` (Mac) or `Ctrl-i` (Windows/Linux) to make it _italic_.
* Click a link somewhere on this page to navigate there. When you link to a non-existent page it will initially show up in orange (to indicate it does not yet exist), but once you click it — you will create the page automatically (only for real when you actually enter some text).
* Start typing `[[` somewhere to insert your own page link (with completion).
* Start typing `:party` to trigger the emoji picker 🎉
* Type `/` somewhere in the text to invoke a **slash command**.
* Type `/` somewhere in the text to invoke a [[Slash Commands|slash command]].
* If this is matching your personality type, you can click this button {[Editor: Toggle Vim Mode]} to toggle Vim mode. If you cannot figure out how to exit it, just click that button again. _Phew!_
Notice that as you move your cursor around on this page and you get close to or “inside” marked up text, you will get to see the underlying [[Markdown]] code. This experience is what we refer to as “live preview” generally your text looks clean, but you still can see whats under the covers and edit it directly, as opposed to [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG) that some other applications use. To move your cursor somewhere using your mouse without navigating or activating (e.g. a wiki, regular link, or a button) hold `Alt` when you click. Holding `Cmd` or `Ctrl` when clicking a link will open it in a new tab or window.
Notice that as you move your cursor around on this page and you get close to or “inside” marked up text, you will get to see the underlying [[Markdown]] code. This experience is what we refer to as [[Live Preview]] generally your text looks clean, but you still can see whats under the covers and edit it directly, as opposed to [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG) that some other applications use. To move your cursor somewhere using your mouse without navigating or activating (e.g. a wiki, regular link, or a button) hold `Alt` when you click. Holding `Cmd` or `Ctrl` when clicking a link will open it in a new tab or window.
Hadnt we mentioned [[Markdown]] yet? Yeah, thats the markup language youll use to add that dash of markup to your documents. Its pretty simple to learn if you dont know it already.
Hadnt we mentioned [[Markdown]] yet? Yeah, thats the markup language youll use to add that dash of markup to your documents. On top of baseline markdown, SilverBullet supports a number of [[Markdown/Extensions]] as well.
You will notice this whole page section is wrapped in a strange type of block. This is a SilverBullet specific feature called a [[Live Templates]], which embeds another (sometime external) page into the existing one. If you hover over this section, youll notice a small _refresh_ and _edit_ button. Hit that edit button to reveal the underlying source that renders this content.
You will notice this whole page section is wrapped in a strange type of block. This is a SilverBullet specific feature called a [[Transclusions]], which embed another (sometime external) page into the existing one.
SilverBullet has even more tricks up its sleeve. Consider [[Live Queries]] which allow you to query [[Objects]] in your space easily.
SilverBullet has more tricks up its sleeve, we are just getting started. Consider [[Live Queries]] which allow you to query [[Objects]] in your space easily. [[Live Templates]] are another source of 🤯.
Dont believe me? Check this out, heres a list of (max 10) pages in your space ordered by name, it updates (somewhat) dynamically 🤯. Create some new pages and come back here to see that it works:
As a quick taster, check this out: heres a list of (max 5) pages in your space ordered by the date of last modification. t updates (somewhat) dynamically. Create some new pages and come back here to see that it works:
```query
page select name order by name limit 10
page select name order by lastModified limit 5
```
## What next?
@ -51,6 +53,6 @@ To keep up with the latest and greatest goings-on in SilverBullet land, keep an
Got any more questions? Join our [community](https://community.silverbullet.md/).
Feel a bit more ready in this endeavor? If so, feel to remove the [[Live Templates|live template]] that renders this on-boarding description. Youre on your own now.
Feel a bit more ready in this endeavor? If so, feel to remove [[Transclusions|transclusion]] that renders this on-boarding description. Youre on your own now.
You got this.
You got this. We believe in you.

View File

@ -1,4 +1,4 @@
A lot of useful functionality in SilverBullet is implemented through [[Templates]], as well as regular [[Pages]]. Some of these you will create yourself for your own specific use, but many are generic and generally useful. Libraries offer a way to _distribute_ sets of templates and pages easily.
A lot of useful functionality in SilverBullet is implemented through [[Templates]], as well as regular [[Pages]]. Some of these you will create yourself for your own specific use, but many are generic and generally useful. Libraries offer a way to _distribute_ sets of templates and pages easily, but also help to give some guidance on how to structure templates for your personal use.
# Whats in a library
Here are some things that a library may provide:
@ -7,10 +7,62 @@ Here are some things that a library may provide:
* Useful widgets such as [[Table of Contents]] or [[Linked Mentions]]
* Useful pages that help you perform maintenance on your space, like detecting broken links, such as [[Library/Core/Page/Maintenance]].
# What libraries are on offer?
# Configuring libraries
Libraries are managed by specifying them in [[SETTINGS]] under the `libraries` attribute. The paths used to libraries are using [[Federation]] page reference syntax.
When you set up a fresh space, the [[Library/Core]] is automatically configured:
```yaml
libraries:
- source: "[[!silverbullet.md/Library/Core/*]]"
```
If you would like to _exclude_ specific pages, for instance [[Library/Core/Widget/Table of Contents]], you can do so using the librarys `exclude` attribute
```yaml
libraries:
- source: "[[!silverbullet.md/Library/Core/*]]"
exclude:
- "[[!silverbullet.md/Library/Core/Widget/Table of Contents]]"
```
In these paths, you can also use the `*` wildcard:
```yaml
- "[[!silverbullet.md/Library/Core/Widget/*]]"
```
This would exclude all widgets specified in the Core library.
# Updating libraries
To update all libraries from their original source (we recommend doing this regularly to get the latest updates), run the {[Libraries: Update]} command.
Updating does the following for each library:
1. It _deletes_ all pages under its local source prefix in your space. For instance for the Core library, this would be `Library/Core/*`. This means that you should **not make local changes** to these files, because they will be wiped out on every update.
2. It downloads fresh copies from the `source` and puts them in the local source prefix (for `!silverbullet.md/Library/Core/*` this would be `Library/Core/`).
3. It performs a {[System: Reload]} so all new commands and configurations are immediately available.
# Where are libraries stored?
Libraries are kept in your spaces `Library/` folder. Since these are just regular [[Meta Pages]], the just live along the rest of your content. For instance, the [[Library/Core]] is kept under `Library/Core`. This is _largely_ a convention, but one worth to sticking to.
If you would like to keep your own templates as a library as well, we recommend putting them in `Library/Personal` as a convention. Then, when you identify a set that may be valuable to other people, you can move them elsewhere under `Library/`.
# Can I change libraries locally?
Technically there is nothing stopping you from making local changes to library content, however **these changes would be reset on every library update**.
Therefore, the recommended approach when requiring to make local adjustments is:
1. Make a copy (copy & paste or {[Page: Copy]}) of the page or template you want to change _outside_ the original library path, e.g. under `Library/Personal`.
2. _Exclude_ the page in question using the `exclude` attribute explained under [[#Configuring libraries]] and then run {[Libraries: Update]} again, to remove the excluded (non-modified) version of the page from your space.
3. Profit
# How to host a library?
This is a topic for exploration. The way it is done here at `silverbullet.md` is by running a SilverBullet instance with the libraries in read-only mode (see [[Install/Configuration#Run mode]]). However, we should explore easier ways to do this. Come talk to us in [the SilverBullet Community](https://community.silverbullet.md/) if youre interested.
# What libraries exist?
Libraries are still a young concept in SilverBullet and therefore were still exploring how to organize and structure these.
Currently, we have the following libraries available:
* [[Library/Core]]: this is the library you want to import _for sure_. Just do it.
* [[Library/Core]]: this is the library you want to use _for sure_. Just do it.
* [[Library/Journal]]: for the journalers among us.

View File

@ -1,4 +1,4 @@
This library is highly recommended for everybody to import immediately. It provides a lot of functionality youll likely appreciate.
This library is highly recommended for everybody to import. It provides a lot of basic functionality in SilverBullet that youll appreciate.
Some examples:
* [[Table of Contents]]
@ -8,9 +8,14 @@ Some examples:
* Some useful general purpose pages such as [[Library/Core/Page/Maintenance]], [[Library/Core/Quick Notes]] and [[Library/Core/Page/Template Index]].
# Installation
To import this library, run the {[Library: Import]} command in your SilverBullet space and enter:
In your [[SETTINGS]] list the following under `libraries:`
```yaml
libraries:
- source: "[[!silverbullet.md/Library/Core/*]]"
```
Then run the {[Libraries: Update]} command to install it.
!silverbullet.md/Library/Core/
See [[Libraries#Configuring libraries]] for more details.
# Included templates
```query

View File

@ -1,9 +1,14 @@
This library contains some useful page templates for journalers. Want to easily create a daily or weekly note? These templates can get you started. Instantiate them via {[Page: From Template]}.
This [[Libraries|library]] contains some useful page templates for journalers. Want to easily create a daily or weekly note? These templates can get you started. Instantiate them via {[Page: From Template]}.
# Installation
To import this library, run the {[Library: Import]} command in your SilverBullet space and enter:
In your [[SETTINGS]] list the following under `libraries:`
```yaml
libraries:
- source: "[[!silverbullet.md/Library/Journal/*]]"
```
Then run the {[Libraries: Update]} command to install it.
!silverbullet.md/Library/Journal/
See [[Libraries#Configuring libraries]] for more details.
# Included templates
```query

View File

@ -7,7 +7,7 @@ In addition to supporting [[Markdown/Basics|markdown basics]] as standardized by
* [[Live Queries]]
* [[Live Templates]]
* [[Table of Contents]]
* [[Transclusion]] syntax
* [[Transclusions]] syntax
* [[Markdown/Anchors]]
* [[Markdown/Admonitions]]
* Hashtags, e.g. `#mytag`.

View File

@ -1,13 +1,19 @@
---
tags: meta
---
#meta
This page contains settings for configuring SilverBullet and its Plugs. Changing any of these will go into effect immediately in most cases except `indexPage` which requires a reload.
This page contains settings for configuring SilverBullet and its Plugs. Changing any of these will go into effect immediately in most cases except `indexPage` which requires a page reload.
```yaml
# Initial page to load when launching SB, can contain template variables
indexPage: "[[SilverBullet]]"
libraries:
# The "Core" library is recommended for all users
- source: "[[!silverbullet.md/Library/Core/*]]"
# You can exclude items from the import using exclude (also supports wildcards):
# exclude:
# - [[!silverbullet.md/Table of Contents]]
# - [[!silverbullet.md/Library/Core/Widget/*]]
## UI TWEAKS
# Hide the sync button
hideSyncButton: false
# Hide the edit button (available on mobile only)