SilverBullet pivot to become an offline-first PWA (#403)
This commit is contained in:
+9
-7
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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> {
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user