diff --git a/plugs/core/page.ts b/plugs/core/page.ts index 4ea16979..3e2dcece 100644 --- a/plugs/core/page.ts +++ b/plugs/core/page.ts @@ -16,6 +16,8 @@ import { applyQuery } from "$sb/lib/query.ts"; import { invokeFunction } from "$sb/silverbullet-syscall/system.ts"; import type { Message } from "$sb/types.ts"; import { sleep } from "../../common/async_util.ts"; +import { cacheFileListing } from "../federation/federation.ts"; +import type { PageMeta } from "../../web/types.ts"; // Key space: // meta: => metaJson @@ -95,7 +97,28 @@ export async function pageComplete(completeEvent: CompleteEvent) { if (!match) { return null; } - const allPages = await space.listPages(); + let allPages: PageMeta[] = await space.listPages(); + const prefix = match[1]; + if (prefix.startsWith("!")) { + // Federation prefix, let's first see if we're matching anything from federation that is locally synced + const prefixMatches = allPages.filter((pageMeta) => + pageMeta.name.startsWith(prefix) + ); + if (prefixMatches.length === 0) { + // Ok, nothing synced in via federation, let's see if this URI is complete enough to try to fetch index.json + if (prefix.includes("/")) { + // Yep + const domain = prefix.split("/")[0]; + // Cached listing + allPages = (await cacheFileListing(domain)).filter((fm) => + fm.name.endsWith(".md") + ).map((fm) => ({ + ...fm, + name: fm.name.slice(0, -3), + })); + } + } + } return { from: completeEvent.pos - match[1].length, options: allPages.map((pageMeta) => { diff --git a/plugs/federation/federation.ts b/plugs/federation/federation.ts index 92b6cd28..99986db2 100644 --- a/plugs/federation/federation.ts +++ b/plugs/federation/federation.ts @@ -43,60 +43,8 @@ export async function listFiles(): Promise { // Fetch them all in parallel try { await Promise.all((await readFederationConfigs()).map(async (config) => { - const cachedListing = await store.get( - `${fileListingPrefixCacheKey}${config.uri}`, - ) as FileListingCacheEntry; - if ( - cachedListing && - cachedListing.lastUpdated > Date.now() - listingCacheTimeout - ) { - fileMetas = fileMetas.concat(cachedListing.items); - return; - } - console.log("Fetching listing from federated", config); - const uriParts = config.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, - ); - - const r = await nativeFetch(indexUrl, { - method: "GET", - headers: { - Accept: "application/json", - }, - 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: config.perm || "ro", - name: `${rootUri}/${meta.name}`, - })); - await store.set(`${fileListingPrefixCacheKey}${config.uri}`, { - items, - lastUpdated: Date.now(), - } as FileListingCacheEntry); - fileMetas = fileMetas.concat(items); - } catch (e: any) { - console.error("Failed to process", indexUrl, e); - if (cachedListing) { - console.info("Using cached listing"); - fileMetas = fileMetas.concat(cachedListing.items); - } - } + const items = await cacheFileListing(config.uri); + fileMetas = fileMetas.concat(items); })); // console.log("All of em: ", fileMetas); @@ -107,6 +55,65 @@ export async function listFiles(): Promise { } } +export async function cacheFileListing(uri: string): Promise { + const cachedListing = await store.get( + `${fileListingPrefixCacheKey}${uri}`, + ) as FileListingCacheEntry; + 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, + ); + + const r = await nativeFetch(indexUrl, { + method: "GET", + headers: { + Accept: "application/json", + "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 store.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; + } + } + return []; +} + export async function readFile( name: string, ): Promise<{ data: Uint8Array; meta: FileMeta } | undefined> {