Federation progress

pull/503/head
Zef Hemel 2023-07-29 23:41:37 +02:00
parent 272bd90ee6
commit fe4887dc78
11 changed files with 112 additions and 49 deletions

View File

@ -22,10 +22,10 @@ export class FallbackSpacePrimitives implements SpacePrimitives {
try {
return await this.primary.readFile(name);
} catch (e) {
console.info(
`Could not read file ${name} from primary, trying fallback, primary read error:`,
e.message,
);
// console.info(
// `Could not read file ${name} from primary, trying fallback, primary read error:`,
// e.message,
// );
try {
const result = await this.fallback.readFile(name);
return {
@ -43,10 +43,10 @@ export class FallbackSpacePrimitives implements SpacePrimitives {
try {
return await this.primary.getFileMeta(name);
} catch (e) {
console.info(
`Could not fetch file ${name} metadata from primary, trying fallback, primary read error`,
e.message,
);
// console.info(
// `Could not fetch file ${name} metadata from primary, trying fallback, primary read error`,
// e.message,
// );
try {
const meta = await this.fallback.getFileMeta(name);
return { ...meta, neverSync: true };

View File

@ -0,0 +1,20 @@
import { resolvePath } from "$sb/lib/resolve.ts";
import { assertEquals } from "../../test_deps.ts";
Deno.test("Test URL resolver", () => {
assertEquals(resolvePath("test", "some page"), "some page");
assertEquals(
resolvePath("!silverbullet.md", "some page"),
"!silverbullet.md/some page",
);
assertEquals(
resolvePath("!silverbullet.md/some/deep/path", "some page"),
"!silverbullet.md/some page",
);
assertEquals(resolvePath("!bla/bla", "!bla/bla2"), "!bla/bla2");
assertEquals(
resolvePath("!silverbullet.md", "test/image.png", true),
"https://silverbullet.md/test/image.png",
);
});

20
plug-api/lib/resolve.ts Normal file
View File

@ -0,0 +1,20 @@
export function resolvePath(
currentPage: string,
pathToResolve: string,
fullUrl = false,
): string {
if (currentPage.startsWith("!") && !pathToResolve.startsWith("!")) {
let domainPart = currentPage.split("/")[0];
if (fullUrl) {
domainPart = domainPart.substring(1);
if (domainPart.startsWith("localhost")) {
domainPart = "http://" + domainPart;
} else {
domainPart = "https://" + domainPart;
}
}
return `${domainPart}/${pathToResolve}`;
} else {
return pathToResolve;
}
}

View File

@ -7,6 +7,7 @@ import {
nodeAtPos,
ParseTree,
} from "$sb/lib/tree.ts";
import { resolvePath } from "$sb/lib/resolve.ts";
async function actionClickOrActionEnter(
mdTree: ParseTree | null,
@ -46,6 +47,7 @@ async function actionClickOrActionEnter(
pos = +pos;
}
}
pageLink = resolvePath(currentPage, pageLink);
if (!pageLink) {
pageLink = currentPage;
}
@ -77,7 +79,7 @@ async function actionClickOrActionEnter(
return editor.flashNotification("Empty link, ignoring", "error");
}
if (url.indexOf("://") === -1 && !url.startsWith("mailto:")) {
return editor.openUrl(decodeURI(url));
return editor.openUrl(resolvePath(currentPage, decodeURI(url)));
} else {
await editor.openUrl(url);
}

View File

@ -17,28 +17,28 @@ async function responseToFileMeta(
r: Response,
name: string,
): Promise<FileMeta> {
let perm = r.headers.get("X-Permission") as any || "ro";
const federationConfigs = await readFederationConfigs();
const federationConfig = federationConfigs.find((config) =>
name.startsWith(config.uri)
);
if (federationConfig?.perm) {
perm = federationConfig.perm;
}
// const perm = r.headers.get("X-Permission") as any || "ro";
// const federationConfigs = await readFederationConfigs();
// const federationConfig = federationConfigs.find((config) =>
// name.startsWith(config.uri)
// );
// if (federationConfig?.perm) {
// perm = federationConfig.perm;
// }
return {
name: name,
size: r.headers.get("Content-length")
? +r.headers.get("Content-length")!
: 0,
contentType: r.headers.get("Content-type")!,
perm: perm,
perm: "ro",
lastModified: +(r.headers.get("X-Last-Modified") || "0"),
};
}
type FederationConfig = {
uri: string;
perm?: "ro" | "rw";
// perm?: "ro" | "rw";
};
let federationConfigs: FederationConfig[] = [];
let lastFederationUrlFetch = 0;
@ -71,7 +71,7 @@ export async function listFiles(): Promise<FileMeta[]> {
(await r.json()).filter((meta: FileMeta) => meta.name.startsWith(prefix))
.map((meta: FileMeta) => ({
...meta,
perm: config.perm || meta.perm,
perm: "ro", //config.perm || meta.perm,
name: `${rootUri}/${meta.name}`,
})),
);
@ -121,30 +121,33 @@ export async function writeFile(
name: string,
data: Uint8Array,
): Promise<FileMeta> {
const url = resolveFederated(name);
console.log("Writing federation file", url);
throw new Error("Writing federation file, not yet supported");
// const url = resolveFederated(name);
// console.log("Writing federation file", url);
const r = await nativeFetch(url, {
method: "PUT",
body: data,
});
const fileMeta = await responseToFileMeta(r, name);
if (!r.ok) {
throw new Error("Could not write file");
}
// const r = await nativeFetch(url, {
// method: "PUT",
// body: data,
// });
// const fileMeta = await responseToFileMeta(r, name);
// if (!r.ok) {
// throw new Error("Could not write file");
// }
return fileMeta;
// return fileMeta;
}
export async function deleteFile(
name: string,
): Promise<void> {
console.log("Deleting federation file", name);
const url = resolveFederated(name);
const r = await nativeFetch(url, { method: "DELETE" });
if (!r.ok) {
throw Error("Failed to delete file");
}
throw new Error("Writing federation file, not yet supported");
// console.log("Deleting federation file", name);
// const url = resolveFederated(name);
// const r = await nativeFetch(url, { method: "DELETE" });
// if (!r.ok) {
// throw Error("Failed to delete file");
// }
}
export async function getFileMeta(name: string): Promise<FileMeta> {

View File

@ -2,11 +2,13 @@ import { editor, system } from "$sb/silverbullet-syscall/mod.ts";
import { asset, store } from "$sb/plugos-syscall/mod.ts";
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
import { renderMarkdownToHtml } from "./markdown_render.ts";
import { resolvePath } from "$sb/lib/resolve.ts";
export async function updateMarkdownPreview() {
if (!(await store.get("enableMarkdownPreview"))) {
return;
}
const currentPage = await editor.getCurrentPage();
const text = await editor.getText();
const mdTree = await parseMarkdown(text);
// const cleanMd = await cleanMarkdown(text);
@ -17,7 +19,7 @@ export async function updateMarkdownPreview() {
annotationPositions: true,
translateUrls: (url) => {
if (!url.includes("://")) {
return decodeURI(url);
url = resolvePath(currentPage, decodeURI(url), true);
}
return url;
},

View File

@ -352,7 +352,17 @@ export class HttpServer {
} else {
url = `https://${url}`;
}
response.redirect(url);
try {
const req = await fetch(url);
response.status = req.status;
response.headers = req.headers;
response.body = req.body;
} catch (e: any) {
console.error("Error fetching federated link", e);
response.status = 500;
response.body = e.message;
}
// response.redirect(url);
return;
}
try {

View File

@ -159,8 +159,8 @@ export class Client {
);
}
this.initNavigator();
await this.loadPlugs();
this.initNavigator();
this.initSync();
this.loadCustomStyles().catch(console.error);

View File

@ -7,14 +7,14 @@ import {
} from "../deps.ts";
import { decoratorStateField } from "./util.ts";
import type { Space } from "../space.ts";
import type { Client } from "../client.ts";
import { resolvePath } from "$sb/lib/resolve.ts";
class InlineImageWidget extends WidgetType {
constructor(
readonly url: string,
readonly title: string,
readonly space: Space,
readonly client: Client,
) {
super();
// console.log("Creating widget", url);
@ -25,22 +25,25 @@ class InlineImageWidget extends WidgetType {
}
get estimatedHeight(): number {
const cachedHeight = this.space.getCachedImageHeight(this.url);
const cachedHeight = this.client.space.getCachedImageHeight(this.url);
// console.log("Estimated height requested", this.url, cachedHeight);
return cachedHeight;
}
toDOM() {
const img = document.createElement("img");
let url = this.url;
url = resolvePath(this.client.currentPage!, url, true);
console.log("Resolving image to", url);
// console.log("Creating DOM", this.url);
const cachedImageHeight = this.space.getCachedImageHeight(this.url);
const cachedImageHeight = this.client.space.getCachedImageHeight(url);
img.onload = () => {
// console.log("Loaded", this.url, "with height", img.height);
if (img.height !== cachedImageHeight) {
this.space.setCachedImageHeight(this.url, img.height);
this.client.space.setCachedImageHeight(url, img.height);
}
};
img.src = this.url;
img.src = url;
img.alt = this.title;
img.title = this.title;
img.style.display = "block";
@ -53,7 +56,7 @@ class InlineImageWidget extends WidgetType {
}
}
export function inlineImagesPlugin(editor: Client) {
export function inlineImagesPlugin(client: Client) {
return decoratorStateField((state: EditorState) => {
const widgets: Range<Decoration>[] = [];
const imageRegex = /!\[(?<title>[^\]]*)\]\((?<url>.+)\)/;
@ -78,7 +81,7 @@ export function inlineImagesPlugin(editor: Client) {
}
widgets.push(
Decoration.widget({
widget: new InlineImageWidget(url, title, editor.space),
widget: new InlineImageWidget(url, title, client),
block: true,
}).range(node.to),
);

View File

@ -9,6 +9,7 @@ import { renderMarkdownToHtml } from "../../plugs/markdown/markdown_render.ts";
import { ParseTree } from "$sb/lib/tree.ts";
import { lezerToParseTree } from "../../common/markdown_parser/parse_tree.ts";
import type { Client } from "../client.ts";
import { resolvePath } from "$sb/lib/resolve.ts";
class TableViewWidget extends WidgetType {
constructor(
@ -39,7 +40,7 @@ class TableViewWidget extends WidgetType {
annotationPositions: true,
translateUrls: (url) => {
if (!url.includes("://")) {
return `/${url}`;
url = resolvePath(this.editor.currentPage!, decodeURI(url), true);
}
return url;
},

View File

@ -8,6 +8,7 @@ import {
isCursorInRange,
LinkWidget,
} from "./util.ts";
import { resolvePath } from "$sb/lib/resolve.ts";
/**
* Plugin to hide path prefix when the cursor is not inside.
@ -33,6 +34,7 @@ export function cleanWikiLinkPlugin(editor: Client) {
if (page.includes("@")) {
cleanPage = page.split("@")[0];
}
cleanPage = resolvePath(editor.currentPage!, cleanPage);
// console.log("Resolved page", resolvedPage);
for (const pageMeta of allPages) {
if (pageMeta.name === cleanPage) {