Federation progress
parent
272bd90ee6
commit
fe4887dc78
|
@ -22,10 +22,10 @@ export class FallbackSpacePrimitives implements SpacePrimitives {
|
||||||
try {
|
try {
|
||||||
return await this.primary.readFile(name);
|
return await this.primary.readFile(name);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.info(
|
// console.info(
|
||||||
`Could not read file ${name} from primary, trying fallback, primary read error:`,
|
// `Could not read file ${name} from primary, trying fallback, primary read error:`,
|
||||||
e.message,
|
// e.message,
|
||||||
);
|
// );
|
||||||
try {
|
try {
|
||||||
const result = await this.fallback.readFile(name);
|
const result = await this.fallback.readFile(name);
|
||||||
return {
|
return {
|
||||||
|
@ -43,10 +43,10 @@ export class FallbackSpacePrimitives implements SpacePrimitives {
|
||||||
try {
|
try {
|
||||||
return await this.primary.getFileMeta(name);
|
return await this.primary.getFileMeta(name);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.info(
|
// console.info(
|
||||||
`Could not fetch file ${name} metadata from primary, trying fallback, primary read error`,
|
// `Could not fetch file ${name} metadata from primary, trying fallback, primary read error`,
|
||||||
e.message,
|
// e.message,
|
||||||
);
|
// );
|
||||||
try {
|
try {
|
||||||
const meta = await this.fallback.getFileMeta(name);
|
const meta = await this.fallback.getFileMeta(name);
|
||||||
return { ...meta, neverSync: true };
|
return { ...meta, neverSync: true };
|
||||||
|
|
|
@ -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",
|
||||||
|
);
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import {
|
||||||
nodeAtPos,
|
nodeAtPos,
|
||||||
ParseTree,
|
ParseTree,
|
||||||
} from "$sb/lib/tree.ts";
|
} from "$sb/lib/tree.ts";
|
||||||
|
import { resolvePath } from "$sb/lib/resolve.ts";
|
||||||
|
|
||||||
async function actionClickOrActionEnter(
|
async function actionClickOrActionEnter(
|
||||||
mdTree: ParseTree | null,
|
mdTree: ParseTree | null,
|
||||||
|
@ -46,6 +47,7 @@ async function actionClickOrActionEnter(
|
||||||
pos = +pos;
|
pos = +pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pageLink = resolvePath(currentPage, pageLink);
|
||||||
if (!pageLink) {
|
if (!pageLink) {
|
||||||
pageLink = currentPage;
|
pageLink = currentPage;
|
||||||
}
|
}
|
||||||
|
@ -77,7 +79,7 @@ async function actionClickOrActionEnter(
|
||||||
return editor.flashNotification("Empty link, ignoring", "error");
|
return editor.flashNotification("Empty link, ignoring", "error");
|
||||||
}
|
}
|
||||||
if (url.indexOf("://") === -1 && !url.startsWith("mailto:")) {
|
if (url.indexOf("://") === -1 && !url.startsWith("mailto:")) {
|
||||||
return editor.openUrl(decodeURI(url));
|
return editor.openUrl(resolvePath(currentPage, decodeURI(url)));
|
||||||
} else {
|
} else {
|
||||||
await editor.openUrl(url);
|
await editor.openUrl(url);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,28 +17,28 @@ async function responseToFileMeta(
|
||||||
r: Response,
|
r: Response,
|
||||||
name: string,
|
name: string,
|
||||||
): Promise<FileMeta> {
|
): Promise<FileMeta> {
|
||||||
let perm = r.headers.get("X-Permission") as any || "ro";
|
// const perm = r.headers.get("X-Permission") as any || "ro";
|
||||||
const federationConfigs = await readFederationConfigs();
|
// const federationConfigs = await readFederationConfigs();
|
||||||
const federationConfig = federationConfigs.find((config) =>
|
// const federationConfig = federationConfigs.find((config) =>
|
||||||
name.startsWith(config.uri)
|
// name.startsWith(config.uri)
|
||||||
);
|
// );
|
||||||
if (federationConfig?.perm) {
|
// if (federationConfig?.perm) {
|
||||||
perm = federationConfig.perm;
|
// perm = federationConfig.perm;
|
||||||
}
|
// }
|
||||||
return {
|
return {
|
||||||
name: name,
|
name: name,
|
||||||
size: r.headers.get("Content-length")
|
size: r.headers.get("Content-length")
|
||||||
? +r.headers.get("Content-length")!
|
? +r.headers.get("Content-length")!
|
||||||
: 0,
|
: 0,
|
||||||
contentType: r.headers.get("Content-type")!,
|
contentType: r.headers.get("Content-type")!,
|
||||||
perm: perm,
|
perm: "ro",
|
||||||
lastModified: +(r.headers.get("X-Last-Modified") || "0"),
|
lastModified: +(r.headers.get("X-Last-Modified") || "0"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type FederationConfig = {
|
type FederationConfig = {
|
||||||
uri: string;
|
uri: string;
|
||||||
perm?: "ro" | "rw";
|
// perm?: "ro" | "rw";
|
||||||
};
|
};
|
||||||
let federationConfigs: FederationConfig[] = [];
|
let federationConfigs: FederationConfig[] = [];
|
||||||
let lastFederationUrlFetch = 0;
|
let lastFederationUrlFetch = 0;
|
||||||
|
@ -71,7 +71,7 @@ export async function listFiles(): Promise<FileMeta[]> {
|
||||||
(await r.json()).filter((meta: FileMeta) => meta.name.startsWith(prefix))
|
(await r.json()).filter((meta: FileMeta) => meta.name.startsWith(prefix))
|
||||||
.map((meta: FileMeta) => ({
|
.map((meta: FileMeta) => ({
|
||||||
...meta,
|
...meta,
|
||||||
perm: config.perm || meta.perm,
|
perm: "ro", //config.perm || meta.perm,
|
||||||
name: `${rootUri}/${meta.name}`,
|
name: `${rootUri}/${meta.name}`,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
@ -121,30 +121,33 @@ export async function writeFile(
|
||||||
name: string,
|
name: string,
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
): Promise<FileMeta> {
|
): Promise<FileMeta> {
|
||||||
const url = resolveFederated(name);
|
throw new Error("Writing federation file, not yet supported");
|
||||||
console.log("Writing federation file", url);
|
// const url = resolveFederated(name);
|
||||||
|
// console.log("Writing federation file", url);
|
||||||
|
|
||||||
const r = await nativeFetch(url, {
|
// const r = await nativeFetch(url, {
|
||||||
method: "PUT",
|
// method: "PUT",
|
||||||
body: data,
|
// body: data,
|
||||||
});
|
// });
|
||||||
const fileMeta = await responseToFileMeta(r, name);
|
// const fileMeta = await responseToFileMeta(r, name);
|
||||||
if (!r.ok) {
|
// if (!r.ok) {
|
||||||
throw new Error("Could not write file");
|
// throw new Error("Could not write file");
|
||||||
}
|
// }
|
||||||
|
|
||||||
return fileMeta;
|
// return fileMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteFile(
|
export async function deleteFile(
|
||||||
name: string,
|
name: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
console.log("Deleting federation file", name);
|
throw new Error("Writing federation file, not yet supported");
|
||||||
const url = resolveFederated(name);
|
|
||||||
const r = await nativeFetch(url, { method: "DELETE" });
|
// console.log("Deleting federation file", name);
|
||||||
if (!r.ok) {
|
// const url = resolveFederated(name);
|
||||||
throw Error("Failed to delete file");
|
// const r = await nativeFetch(url, { method: "DELETE" });
|
||||||
}
|
// if (!r.ok) {
|
||||||
|
// throw Error("Failed to delete file");
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFileMeta(name: string): Promise<FileMeta> {
|
export async function getFileMeta(name: string): Promise<FileMeta> {
|
||||||
|
|
|
@ -2,11 +2,13 @@ import { editor, system } from "$sb/silverbullet-syscall/mod.ts";
|
||||||
import { asset, store } from "$sb/plugos-syscall/mod.ts";
|
import { asset, store } from "$sb/plugos-syscall/mod.ts";
|
||||||
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
|
import { parseMarkdown } from "$sb/silverbullet-syscall/markdown.ts";
|
||||||
import { renderMarkdownToHtml } from "./markdown_render.ts";
|
import { renderMarkdownToHtml } from "./markdown_render.ts";
|
||||||
|
import { resolvePath } from "$sb/lib/resolve.ts";
|
||||||
|
|
||||||
export async function updateMarkdownPreview() {
|
export async function updateMarkdownPreview() {
|
||||||
if (!(await store.get("enableMarkdownPreview"))) {
|
if (!(await store.get("enableMarkdownPreview"))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const currentPage = await editor.getCurrentPage();
|
||||||
const text = await editor.getText();
|
const text = await editor.getText();
|
||||||
const mdTree = await parseMarkdown(text);
|
const mdTree = await parseMarkdown(text);
|
||||||
// const cleanMd = await cleanMarkdown(text);
|
// const cleanMd = await cleanMarkdown(text);
|
||||||
|
@ -17,7 +19,7 @@ export async function updateMarkdownPreview() {
|
||||||
annotationPositions: true,
|
annotationPositions: true,
|
||||||
translateUrls: (url) => {
|
translateUrls: (url) => {
|
||||||
if (!url.includes("://")) {
|
if (!url.includes("://")) {
|
||||||
return decodeURI(url);
|
url = resolvePath(currentPage, decodeURI(url), true);
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
|
@ -352,7 +352,17 @@ export class HttpServer {
|
||||||
} else {
|
} else {
|
||||||
url = `https://${url}`;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -159,8 +159,8 @@ export class Client {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.initNavigator();
|
|
||||||
await this.loadPlugs();
|
await this.loadPlugs();
|
||||||
|
this.initNavigator();
|
||||||
this.initSync();
|
this.initSync();
|
||||||
|
|
||||||
this.loadCustomStyles().catch(console.error);
|
this.loadCustomStyles().catch(console.error);
|
||||||
|
|
|
@ -7,14 +7,14 @@ import {
|
||||||
} from "../deps.ts";
|
} from "../deps.ts";
|
||||||
import { decoratorStateField } from "./util.ts";
|
import { decoratorStateField } from "./util.ts";
|
||||||
|
|
||||||
import type { Space } from "../space.ts";
|
|
||||||
import type { Client } from "../client.ts";
|
import type { Client } from "../client.ts";
|
||||||
|
import { resolvePath } from "$sb/lib/resolve.ts";
|
||||||
|
|
||||||
class InlineImageWidget extends WidgetType {
|
class InlineImageWidget extends WidgetType {
|
||||||
constructor(
|
constructor(
|
||||||
readonly url: string,
|
readonly url: string,
|
||||||
readonly title: string,
|
readonly title: string,
|
||||||
readonly space: Space,
|
readonly client: Client,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
// console.log("Creating widget", url);
|
// console.log("Creating widget", url);
|
||||||
|
@ -25,22 +25,25 @@ class InlineImageWidget extends WidgetType {
|
||||||
}
|
}
|
||||||
|
|
||||||
get estimatedHeight(): number {
|
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);
|
// console.log("Estimated height requested", this.url, cachedHeight);
|
||||||
return cachedHeight;
|
return cachedHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
toDOM() {
|
toDOM() {
|
||||||
const img = document.createElement("img");
|
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);
|
// console.log("Creating DOM", this.url);
|
||||||
const cachedImageHeight = this.space.getCachedImageHeight(this.url);
|
const cachedImageHeight = this.client.space.getCachedImageHeight(url);
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
// console.log("Loaded", this.url, "with height", img.height);
|
// console.log("Loaded", this.url, "with height", img.height);
|
||||||
if (img.height !== cachedImageHeight) {
|
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.alt = this.title;
|
||||||
img.title = this.title;
|
img.title = this.title;
|
||||||
img.style.display = "block";
|
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) => {
|
return decoratorStateField((state: EditorState) => {
|
||||||
const widgets: Range<Decoration>[] = [];
|
const widgets: Range<Decoration>[] = [];
|
||||||
const imageRegex = /!\[(?<title>[^\]]*)\]\((?<url>.+)\)/;
|
const imageRegex = /!\[(?<title>[^\]]*)\]\((?<url>.+)\)/;
|
||||||
|
@ -78,7 +81,7 @@ export function inlineImagesPlugin(editor: Client) {
|
||||||
}
|
}
|
||||||
widgets.push(
|
widgets.push(
|
||||||
Decoration.widget({
|
Decoration.widget({
|
||||||
widget: new InlineImageWidget(url, title, editor.space),
|
widget: new InlineImageWidget(url, title, client),
|
||||||
block: true,
|
block: true,
|
||||||
}).range(node.to),
|
}).range(node.to),
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { renderMarkdownToHtml } from "../../plugs/markdown/markdown_render.ts";
|
||||||
import { ParseTree } from "$sb/lib/tree.ts";
|
import { ParseTree } from "$sb/lib/tree.ts";
|
||||||
import { lezerToParseTree } from "../../common/markdown_parser/parse_tree.ts";
|
import { lezerToParseTree } from "../../common/markdown_parser/parse_tree.ts";
|
||||||
import type { Client } from "../client.ts";
|
import type { Client } from "../client.ts";
|
||||||
|
import { resolvePath } from "$sb/lib/resolve.ts";
|
||||||
|
|
||||||
class TableViewWidget extends WidgetType {
|
class TableViewWidget extends WidgetType {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -39,7 +40,7 @@ class TableViewWidget extends WidgetType {
|
||||||
annotationPositions: true,
|
annotationPositions: true,
|
||||||
translateUrls: (url) => {
|
translateUrls: (url) => {
|
||||||
if (!url.includes("://")) {
|
if (!url.includes("://")) {
|
||||||
return `/${url}`;
|
url = resolvePath(this.editor.currentPage!, decodeURI(url), true);
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
isCursorInRange,
|
isCursorInRange,
|
||||||
LinkWidget,
|
LinkWidget,
|
||||||
} from "./util.ts";
|
} from "./util.ts";
|
||||||
|
import { resolvePath } from "$sb/lib/resolve.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plugin to hide path prefix when the cursor is not inside.
|
* Plugin to hide path prefix when the cursor is not inside.
|
||||||
|
@ -33,6 +34,7 @@ export function cleanWikiLinkPlugin(editor: Client) {
|
||||||
if (page.includes("@")) {
|
if (page.includes("@")) {
|
||||||
cleanPage = page.split("@")[0];
|
cleanPage = page.split("@")[0];
|
||||||
}
|
}
|
||||||
|
cleanPage = resolvePath(editor.currentPage!, cleanPage);
|
||||||
// console.log("Resolved page", resolvedPage);
|
// console.log("Resolved page", resolvedPage);
|
||||||
for (const pageMeta of allPages) {
|
for (const pageMeta of allPages) {
|
||||||
if (pageMeta.name === cleanPage) {
|
if (pageMeta.name === cleanPage) {
|
||||||
|
|
Loading…
Reference in New Issue