SilverBullet pivot to become an offline-first PWA (#403)

This commit is contained in:
Zef Hemel
2023-05-23 20:53:53 +02:00
committed by GitHub
parent b256269897
commit 5f484bed57
389 changed files with 4484 additions and 291129 deletions
+9 -7
View File
@@ -1,7 +1,3 @@
import type {
FileData,
FileEncoding,
} from "../../common/spaces/space_primitives.ts";
import { renderToText, replaceNodesMatching } from "$sb/lib/tree.ts";
import type { FileMeta } from "../../common/types.ts";
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
@@ -11,8 +7,7 @@ export const cloudPrefix = "💭 ";
export async function readFileCloud(
name: string,
encoding: FileEncoding,
): Promise<{ data: FileData; meta: FileMeta } | undefined> {
): Promise<{ data: string; meta: FileMeta } | undefined> {
const originalUrl = name.substring(
cloudPrefix.length,
name.length - ".md".length,
@@ -43,7 +38,7 @@ export async function readFileCloud(
`${cloudPrefix}${originalUrl.split("/")[0]}/`,
);
return {
data: encoding === "utf8" ? text : base64EncodedDataUrl(
data: base64EncodedDataUrl(
"text/markdown",
new TextEncoder().encode(text),
),
@@ -57,6 +52,13 @@ export async function readFileCloud(
};
}
export function writeFileCloud(
name: string,
): Promise<FileMeta> {
console.log("Writing cloud file", name);
return getFileMetaCloud(name);
}
async function translateLinksWithPrefix(
text: string,
prefix: string,
+8 -63
View File
@@ -1,6 +1,6 @@
name: core
imports:
- https://get.silverbullet.md/global.plug.json
requiredPermissions:
- fetch
syntax:
Hashtag:
firstCharacters:
@@ -26,10 +26,8 @@ functions:
path: "./editor.ts:toggleDarkMode"
command:
name: "Editor: Toggle Dark Mode"
clearPageIndex:
path: "./page.ts:clearPageIndex"
env: server
events:
- page:saved
- page:deleted
@@ -45,9 +43,6 @@ functions:
path: "./page.ts:reindexCommand"
command:
name: "Space: Reindex"
reindexSpace:
path: "./page.ts:reindexSpace"
env: server
deletePage:
path: "./page.ts:deletePage"
command:
@@ -138,40 +133,7 @@ functions:
path: "./anchor.ts:anchorComplete"
events:
- editor:complete
# Full text search
searchIndex:
path: ./search.ts:pageIndex
events:
- page:index
searchUnindex:
path: "./search.ts:pageUnindex"
env: server
events:
- page:deleted
searchQueryProvider:
path: ./search.ts:queryProvider
events:
- query:full-text
searchCommand:
path: ./search.ts:searchCommand
command:
name: "Search Space"
key: Ctrl-Shift-f
mac: Cmd-Shift-f
readPageSearch:
path: ./search.ts:readFileSearch
env: server
pageNamespace:
pattern: "🔍 .+"
operation: readFile
getPageMetaSearch:
path: ./search.ts:getFileMetaSearch
env: server
pageNamespace:
pattern: "🔍 .+"
operation: getFileMeta
# Template commands
insertTemplateText:
path: "./template.ts:insertTemplateText"
@@ -342,9 +304,6 @@ functions:
name: "Plugs: Update"
key: "Ctrl-Shift-p"
mac: "Cmd-Shift-p"
updatePlugs:
path: ./plugmanager.ts:updatePlugs
env: server
getPlugHTTPS:
path: "./plugmanager.ts:getPlugHTTPS"
events:
@@ -367,20 +326,6 @@ functions:
path: ./debug.ts:parsePageCommand
command:
name: "Debug: Parse Document"
showLogsCommand:
path: ./debug.ts:showLogsCommand
command:
name: "Show Logs"
key: "Ctrl-Alt-l"
mac: "Cmd-Alt-l"
hideBhsCommand:
path: ./debug.ts:hideBhsCommand
command:
name: "UI: Hide BHS"
key: "Ctrl-Alt-b"
mac: "Cmd-Alt-b"
events:
- log:hide
# Link unfurl infrastructure
unfurlLink:
@@ -391,9 +336,6 @@ functions:
mac: "Cmd-Shift-u"
contexts:
- NakedURL
unfurlExec:
env: server
path: ./link.ts:unfurlExec
# Title-based link unfurl
titleUnfurlOptions:
@@ -418,13 +360,16 @@ functions:
# Cloud pages
readPageCloud:
path: ./cloud.ts:readFileCloud
env: server
pageNamespace:
pattern: "💭 .+"
operation: readFile
writePageCloud:
path: ./cloud.ts:writeFileCloud
pageNamespace:
pattern: "💭 .+"
operation: writeFile
getPageMetaCloud:
path: ./cloud.ts:getFileMetaCloud
env: server
pageNamespace:
pattern: "💭 .+"
operation: getFileMeta
-80
View File
@@ -10,83 +10,3 @@ export async function parsePageCommand() {
),
);
}
export async function showLogsCommand() {
await editor.showPanel(
"bhs",
1,
`
<style>
#close {
width: 100%;
}
#client-log-header {
position: absolute;
left: 0;
top: 35px;
}
#server-log-header {
position: absolute;
right: 0;
top: 35px;
width: 50%;
}
#client-log {
position: absolute;
left: 0;
top: 60px;
bottom: 0;
width: 50%;
overflow: scroll;
}
#server-log {
position: absolute;
right: 0;
top: 60px;
bottom: 0;
width: 50%;
overflow: scroll;
}
</style>
<button onclick="self.close()" id="close">Close</button>
<div id="client-log-header">Client logs (max 100)</div>
<div id="client-log">Loading...</div>
<div id="server-log-header">Server logs (max 100)</div>
<div id="server-log">Loading...</div>`,
`
const clientDiv = document.getElementById("client-log");
clientDiv.scrollTop = clientDiv.scrollHeight;
const serverDiv = document.getElementById("server-log");
serverDiv.scrollTop = serverDiv.scrollHeight;
self.close = () => {
syscall("event.dispatch", "log:hide");
};
syscall("system.getEnv").then((env) => {
const clientServerMode = !!env;
if (!clientServerMode) {
// Running in hybrid mode (mobile), so let's ignore server logs (they're the same as client logs)
serverDiv.style.display = "none";
clientDiv.style.width = "100%";
document.getElementById("server-log-header").style.display = "none";
}
setInterval(() => {
Promise.resolve().then(async () => {
if(clientServerMode) {
const serverLogs = await syscall("sandbox.getServerLogs");
serverDiv.innerHTML = "<pre>" + serverLogs.map((le) => "[" + le.level + "] " + le.message).join("\\n") + "</pre>";
}
const clientLogs = await syscall("sandbox.getLogs");
clientDiv.innerHTML = "<pre>" + clientLogs.map((le) => "[" + le.level + "] " + le.message).join("\\n") + "</pre>";
}).catch(console.error);
}, 1000);
});
`,
);
}
export async function hideBhsCommand() {
await editor.hidePanel("bhs");
}
+6 -5
View File
@@ -1,18 +1,19 @@
import { clientStore, editor } from "$sb/silverbullet-syscall/mod.ts";
import { editor } from "$sb/silverbullet-syscall/mod.ts";
import { store } from "$sb/plugos-syscall/mod.ts";
// Run on "editor:init"
export async function setEditorMode() {
if (await clientStore.get("vimMode")) {
if (await store.get("vimMode")) {
await editor.setUiOption("vimMode", true);
}
if (await clientStore.get("darkMode")) {
if (await store.get("darkMode")) {
await editor.setUiOption("darkMode", true);
}
}
export async function toggleDarkMode() {
let darkMode = await clientStore.get("darkMode");
let darkMode = await store.get("darkMode");
darkMode = !darkMode;
await editor.setUiOption("darkMode", darkMode);
await clientStore.set("darkMode", darkMode);
await store.set("darkMode", darkMode);
}
+4 -4
View File
@@ -1,4 +1,4 @@
import * as YAML from "yaml";
import { YAML } from "$sb/plugos-syscall/mod.ts";
import type { WidgetContent } from "$sb/app_event.ts";
type EmbedConfig = {
@@ -20,11 +20,11 @@ function extractYoutubeVideoId(url: string) {
return null;
}
export function embedWidget(
export async function embedWidget(
bodyText: string,
): WidgetContent {
): Promise<WidgetContent> {
try {
const data: EmbedConfig = YAML.parse(bodyText) as any;
const data: EmbedConfig = await YAML.parse(bodyText) as any;
let url = data.url;
const youtubeVideoId = extractYoutubeVideoId(url);
if (youtubeVideoId) {
+7 -16
View File
@@ -1,5 +1,5 @@
import { nodeAtPos } from "$sb/lib/tree.ts";
import { editor, markdown, system } from "$sb/silverbullet-syscall/mod.ts";
import { editor, markdown } from "$sb/silverbullet-syscall/mod.ts";
import { events } from "$sb/plugos-syscall/mod.ts";
type UnfurlOption = {
@@ -26,16 +26,17 @@ export async function unfurlCommand() {
return;
}
try {
const replacement = await system.invokeFunction(
"server",
"unfurlExec",
selectedUnfurl.id,
const replacement = await events.dispatchEvent(
`unfurl:${selectedUnfurl.id}`,
url,
);
if (replacement.length === 0) {
throw new Error("Unfurl failed");
}
await editor.replaceRange(
nakedUrlNode?.from!,
nakedUrlNode?.to!,
replacement,
replacement[0],
);
} catch (e: any) {
await editor.flashNotification(e.message, "error");
@@ -51,16 +52,6 @@ export function titleUnfurlOptions(): UnfurlOption[] {
];
}
// Run on the server because plugs will likely rely on fetch for this
export async function unfurlExec(id: string, url: string): Promise<string> {
const replacement = await events.dispatchEvent(`unfurl:${id}`, url);
if (replacement.length === 0) {
throw new Error("Unfurl failed");
} else {
return replacement[0];
}
}
const titleRegex = /<title[^>]*>\s*([^<]+)\s*<\/title\s*>/i;
export async function titleUnfurl(url: string): Promise<string> {
+5 -9
View File
@@ -1,10 +1,5 @@
import type { ClickEvent } from "$sb/app_event.ts";
import {
editor,
markdown,
space,
system,
} from "$sb/silverbullet-syscall/mod.ts";
import { editor, markdown, system } from "$sb/silverbullet-syscall/mod.ts";
import {
addParentPointers,
findNodeOfType,
@@ -82,9 +77,10 @@ async function actionClickOrActionEnter(
}
if (url.indexOf("://") === -1 && !url.startsWith("mailto:")) {
url = decodeURIComponent(url);
// attachment URL, let's fetch as a data url
const dataUrl = await space.readAttachment(url);
return editor.downloadFile(url, dataUrl);
// // attachment URL, let's fetch as a data url
// const dataUrl = await space.readAttachment(url);
// return editor.downloadFile(url, dataUrl);
return editor.openUrl(`/.fs/${url}`);
} else {
await editor.openUrl(url);
}
+10 -6
View File
@@ -23,6 +23,7 @@ import {
} from "$sb/lib/tree.ts";
import { applyQuery } from "$sb/lib/query.ts";
import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
import { invokeFunction } from "../../plug-api/silverbullet-syscall/system.ts";
// Key space:
// pl:toPage:pos => pageName
@@ -31,8 +32,8 @@ import { extractFrontmatter } from "$sb/lib/frontmatter.ts";
export async function indexLinks({ name, tree }: IndexTreeEvent) {
const backLinks: { key: string; value: string }[] = [];
// [[Style Links]]
// console.log("Now indexing", name);
const pageMeta = extractFrontmatter(tree);
// console.log("Now indexing links for", name);
const pageMeta = await extractFrontmatter(tree);
if (Object.keys(pageMeta).length > 0) {
// console.log("Extracted page meta data", pageMeta);
// Don't index meta data starting with $
@@ -44,6 +45,8 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) {
await index.set(name, "meta:", pageMeta);
}
// throw new Error("Boom");
collectNodesMatching(tree, (n) => n.type === "WikiLinkPage").forEach((n) => {
let toPage = n.children![0].text!;
if (toPage.includes("@")) {
@@ -118,7 +121,7 @@ export async function renamePage(cmdDef: any) {
`Page ${newName} already exists, cannot rename to existing page.`,
);
} catch (e: any) {
if (e.message.includes("not found")) {
if (e.message === "Not found") {
// Expected not found error, so we can continue
} else {
await editor.flashNotification(e.message, "error");
@@ -224,7 +227,7 @@ async function getBackLinks(pageName: string): Promise<BackLink[]> {
export async function reindexCommand() {
await editor.flashNotification("Reindexing...");
await system.invokeFunction("server", "reindexSpace");
await reindexSpace();
await editor.flashNotification("Reindexing done");
}
@@ -245,10 +248,11 @@ export async function pageComplete(completeEvent: CompleteEvent) {
};
}
// Server functions
export async function reindexSpace() {
console.log("Clearing page index...");
await index.clearPageIndex();
// Executed this way to not have to embed the search plug code here
await invokeFunction("client", "search.clearIndex");
console.log("Listing all pages");
const pages = await space.listPages();
let counter = 0;
@@ -272,7 +276,7 @@ export async function clearPageIndex(page: string) {
}
export async function parseIndexTextRepublish({ name, text }: IndexEvent) {
console.log("Reindexing", name);
// console.log("Reindexing", name);
await events.dispatchEvent("page:index", {
name,
tree: await markdown.parseMarkdown(text),
+71 -62
View File
@@ -2,6 +2,7 @@ import { events } from "$sb/plugos-syscall/mod.ts";
import type { Manifest } from "../../common/manifest.ts";
import { editor, space, system } from "$sb/silverbullet-syscall/mod.ts";
import { readYamlPage } from "$sb/lib/yaml_page.ts";
import { builtinPlugNames } from "../builtin_plugs.ts";
const plugsPrelude =
"This file lists all plugs that SilverBullet will load. Run the {[Plugs: Update]} command to update and reload this list of plugs.\n\n";
@@ -10,9 +11,69 @@ export async function updatePlugsCommand() {
await editor.save();
await editor.flashNotification("Updating plugs...");
try {
await system.invokeFunction("server", "updatePlugs");
let plugList: string[] = [];
try {
const plugListRead: any[] = await readYamlPage("PLUGS");
plugList = plugListRead.filter((plug) => typeof plug === "string");
if (plugList.length !== plugListRead.length) {
throw new Error(
`Some of the plugs were not in a yaml list format, they were ignored`,
);
}
} catch (e: any) {
if (e.message.includes("Could not read file")) {
console.warn("No PLUGS page found, not loading anything");
return;
}
throw new Error(`Error processing PLUGS: ${e.message}`);
}
console.log("Plug YAML", plugList);
const allCustomPlugNames: string[] = [];
for (const plugUri of plugList) {
const [protocol, ...rest] = plugUri.split(":");
const plugNameMatch = /\/([^\/]+)\.plug\.js$/.exec(plugUri);
if (!plugNameMatch) {
console.error(
"Could not extract plug name from ",
plugUri,
"ignoring...",
);
continue;
}
const plugName = plugNameMatch[1];
const manifests = await events.dispatchEvent(
`get-plug:${protocol}`,
rest.join(":"),
);
if (manifests.length === 0) {
console.error("Could not resolve plug", plugUri);
}
// console.log("Got manifests", plugUri, protocol, manifests);
const workerCode = manifests[0] as string;
allCustomPlugNames.push(plugName);
// console.log("Writing", `_plug/${plugName}.plug.js`, workerCode);
await space.writeAttachment(
`_plug/${plugName}.plug.js`,
"utf8",
workerCode,
);
}
const allPlugNames = [...builtinPlugNames, ...allCustomPlugNames];
// And delete extra ones
for (const existingPlug of await space.listPlugs()) {
const plugName = existingPlug.substring(
"_plug/".length,
existingPlug.length - ".plug.js".length,
);
if (!allPlugNames.includes(plugName)) {
await space.deleteAttachment(existingPlug);
}
}
await editor.flashNotification("And... done!");
system.reloadPlugs();
} catch (e: any) {
editor.flashNotification("Error updating plugs: " + e.message, "error");
}
@@ -45,74 +106,22 @@ export async function addPlugCommand() {
"\n```",
);
await editor.navigate("PLUGS");
await system.invokeFunction("server", "updatePlugs");
await updatePlugsCommand();
await editor.flashNotification("Plug added!");
system.reloadPlugs();
}
export async function updatePlugs() {
let plugList: string[] = [];
try {
const plugListRead: any[] = await readYamlPage("PLUGS");
plugList = plugListRead.filter((plug) => typeof plug === "string");
if (plugList.length !== plugListRead.length) {
throw new Error(
`Some of the plugs were not in a yaml list format, they were ignored`,
);
}
} catch (e: any) {
if (e.message.includes("Could not read file")) {
console.warn("No PLUGS page found, not loading anything");
return;
}
throw new Error(`Error processing PLUGS: ${e.message}`);
}
console.log("Plug YAML", plugList);
const allPlugNames: string[] = [];
for (const plugUri of plugList) {
const [protocol, ...rest] = plugUri.split(":");
const manifests = await events.dispatchEvent(
`get-plug:${protocol}`,
rest.join(":"),
);
if (manifests.length === 0) {
console.error("Could not resolve plug", plugUri);
}
// console.log("Got manifests", plugUri, protocol, manifests);
const manifest = manifests[0];
allPlugNames.push(manifest.name);
// console.log("Writing", `_plug/${manifest.name}`);
await space.writeAttachment(
`_plug/${manifest.name}.plug.json`,
"utf8",
JSON.stringify(manifest),
);
}
// And delete extra ones
for (const existingPlug of await space.listPlugs()) {
const plugName = existingPlug.substring(
"_plug/".length,
existingPlug.length - ".plug.json".length,
);
if (!allPlugNames.includes(plugName)) {
await space.deleteAttachment(existingPlug);
}
}
system.reloadPlugs();
}
export async function getPlugHTTPS(url: string): Promise<Manifest> {
export async function getPlugHTTPS(url: string): Promise<string> {
const fullUrl = `https:${url}`;
console.log("Now fetching plug manifest from", fullUrl);
console.log("Now fetching plug code from", fullUrl);
const req = await fetch(fullUrl);
if (req.status !== 200) {
throw new Error(`Could not fetch plug manifest from ${fullUrl}`);
throw new Error(`Could not fetch plug code from ${fullUrl}`);
}
return req.json();
return req.text();
}
export function getPlugGithub(identifier: string): Promise<Manifest> {
export function getPlugGithub(identifier: string): Promise<string> {
const [owner, repo, path] = identifier.split("/");
let [repoClean, branch] = repo.split("@");
if (!branch) {
@@ -125,7 +134,7 @@ export function getPlugGithub(identifier: string): Promise<Manifest> {
export async function getPlugGithubRelease(
identifier: string,
): Promise<Manifest> {
): Promise<string> {
let [owner, repo, version] = identifier.split("/");
if (!version || version === "latest") {
console.log("fetching the latest version");
@@ -141,6 +150,6 @@ export async function getPlugGithubRelease(
version = result.name;
}
const finalUrl =
`//github.com/${owner}/${repo}/releases/download/${version}/${repo}.plug.json`;
`//github.com/${owner}/${repo}/releases/download/${version}/${repo}.plug.js`;
return getPlugHTTPS(finalUrl);
}
+1 -1
View File
@@ -16,7 +16,7 @@ export async function extractToPage() {
`Page ${newName} already exists, cannot rename to existing page.`,
);
} catch (e: any) {
if (e.message.includes("not found")) {
if (e.message === "Not found") {
// Expected not found error, so we can continue
} else {
await editor.flashNotification(e.message, "error");
-111
View File
@@ -1,111 +0,0 @@
import { fulltext } from "$sb/plugos-syscall/mod.ts";
import { renderToText } from "$sb/lib/tree.ts";
import type { FileMeta } from "../../common/types.ts";
import { editor, index } from "$sb/silverbullet-syscall/mod.ts";
import { IndexTreeEvent, QueryProviderEvent } from "$sb/app_event.ts";
import { applyQuery, removeQueries } from "$sb/lib/query.ts";
import {
FileData,
FileEncoding,
} from "../../common/spaces/space_primitives.ts";
import { base64EncodedDataUrl } from "../../plugos/asset_bundle/base64.ts";
const searchPrefix = "🔍 ";
export async function pageIndex(data: IndexTreeEvent) {
removeQueries(data.tree);
const cleanText = renderToText(data.tree);
await fulltext.fullTextIndex(data.name, cleanText);
}
export async function pageUnindex(pageName: string) {
await fulltext.fullTextDelete(pageName);
}
export async function queryProvider({
query,
}: QueryProviderEvent): Promise<any[]> {
const phraseFilter = query.filter.find((f) => f.prop === "phrase");
if (!phraseFilter) {
throw Error("No 'phrase' filter specified, this is mandatory");
}
let results = await fulltext.fullTextSearch(phraseFilter.value, {
highlightEllipsis: "...",
limit: 100,
});
const allPageMap: Map<string, any> = new Map(
results.map((r: any) => [r.name, r]),
);
for (const { page, value } of await index.queryPrefix("meta:")) {
const p = allPageMap.get(page);
if (p) {
for (const [k, v] of Object.entries(value)) {
p[k] = v;
}
}
}
// Remove the "phrase" filter
query.filter.splice(query.filter.indexOf(phraseFilter), 1);
results = applyQuery(query, results);
return results;
}
export async function searchCommand() {
const phrase = await editor.prompt("Search for: ");
if (phrase) {
await editor.navigate(`${searchPrefix}${phrase}`);
}
}
export async function readFileSearch(
name: string,
encoding: FileEncoding,
): Promise<{ data: FileData; meta: FileMeta }> {
const phrase = name.substring(
searchPrefix.length,
name.length - ".md".length,
);
const results = await fulltext.fullTextSearch(phrase, {
highlightEllipsis: "...",
highlightPostfix: "==",
highlightPrefix: "==",
summaryMaxLength: 30,
limit: 100,
});
const text = `# Search results for "${phrase}"\n${
results
.map((r: any) =>
`[[${r.name}]]:\n> ${r.snippet.split("\n").join("\n> ")}`
)
.join("\n\n")
}
`;
return {
// encoding === "arraybuffer" is not an option, so either it's "utf8" or "dataurl"
data: encoding === "utf8" ? text : base64EncodedDataUrl(
"text/markdown",
new TextEncoder().encode(text),
),
meta: {
name,
contentType: "text/markdown",
size: text.length,
lastModified: 0,
perm: "ro",
},
};
}
export function getFileMetaSearch(name: string): FileMeta {
return {
name,
contentType: "text/markdown",
size: -1,
lastModified: 0,
perm: "ro",
};
}
+1 -1
View File
@@ -14,7 +14,7 @@ import { extractFrontmatter } from "../../plug-api/lib/frontmatter.ts";
export async function indexTags({ name, tree }: IndexTreeEvent) {
removeQueries(tree);
const allTags = new Set<string>();
const { tags } = extractFrontmatter(tree);
const { tags } = await extractFrontmatter(tree);
if (Array.isArray(tags)) {
tags.forEach((t) => allTags.add(t));
}
+14 -17
View File
@@ -31,7 +31,7 @@ export async function instantiateTemplateCommand() {
);
const parseTree = await markdown.parseMarkdown(text);
const additionalPageMeta = extractFrontmatter(parseTree, [
const additionalPageMeta = await extractFrontmatter(parseTree, [
"$name",
"$disableDirectives",
]);
@@ -157,28 +157,25 @@ export async function dailyNoteCommand() {
dailyNoteTemplate: "template/page/Daily Note",
dailyNotePrefix: "📅 ",
});
let dailyNoteTemplateText = "";
try {
dailyNoteTemplateText = await space.readPage(dailyNoteTemplate);
} catch {
console.warn(`No daily note template found at ${dailyNoteTemplate}`);
}
const date = niceDate(new Date());
const pageName = `${dailyNotePrefix}${date}`;
if (dailyNoteTemplateText) {
try {
await space.getPageMeta(pageName);
} catch {
// Doesn't exist, let's create
let dailyNoteTemplateText = "";
try {
await space.getPageMeta(pageName);
dailyNoteTemplateText = await space.readPage(dailyNoteTemplate);
} catch {
// Doesn't exist, let's create
await space.writePage(
pageName,
replaceTemplateVars(dailyNoteTemplateText, pageName),
);
console.warn(`No daily note template found at ${dailyNoteTemplate}`);
}
await editor.navigate(pageName);
} else {
await editor.navigate(pageName);
await space.writePage(
pageName,
replaceTemplateVars(dailyNoteTemplateText, pageName),
);
}
await editor.navigate(pageName);
}
function getWeekStartDate(monday = false) {
+4 -3
View File
@@ -1,11 +1,12 @@
import { readCodeBlockPage } from "../../plug-api/lib/yaml_page.ts";
import { clientStore, editor } from "$sb/silverbullet-syscall/mod.ts";
import { editor } from "$sb/silverbullet-syscall/mod.ts";
import { store } from "$sb/plugos-syscall/mod.ts";
export async function toggleVimMode() {
let vimMode = await clientStore.get("vimMode");
let vimMode = await store.get("vimMode");
vimMode = !vimMode;
await editor.setUiOption("vimMode", vimMode);
await clientStore.set("vimMode", vimMode);
await store.set("vimMode", vimMode);
}
export async function loadVimRc() {