From afa160d2c2e7fae44a404ed71106a52c38cdff72 Mon Sep 17 00:00:00 2001 From: Zef Hemel <zef@zef.me> Date: Sun, 30 Jul 2023 08:56:44 +0200 Subject: [PATCH] Cleanup and federation prep --- common/spaces/fallback_space_primitives.ts | 30 +++++++---- common/types.ts | 2 +- plug-api/lib/resolve.ts | 6 ++- plugs/directive/command.ts | 21 ++++++-- plugs/directive/directives.ts | 8 --- plugs/directive/template_directive.ts | 59 ++++++++++++++++++++-- web/sync_service.ts | 4 +- 7 files changed, 98 insertions(+), 32 deletions(-) diff --git a/common/spaces/fallback_space_primitives.ts b/common/spaces/fallback_space_primitives.ts index a7d9e13..dc0f98d 100644 --- a/common/spaces/fallback_space_primitives.ts +++ b/common/spaces/fallback_space_primitives.ts @@ -22,15 +22,19 @@ 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, - // ); + if (e.message === "Not found") { + console.info("Reading file content from fallback for", name); + } else { + console.warn( + `Could not read file ${name} from primary, trying fallback, primary read error`, + e.message, + ); + } try { const result = await this.fallback.readFile(name); return { data: result.data, - meta: { ...result.meta, neverSync: true }, + meta: { ...result.meta, noSync: true }, }; } catch (fallbackError: any) { console.error("Error during readFile fallback", fallbackError.message); @@ -42,14 +46,18 @@ export class FallbackSpacePrimitives implements SpacePrimitives { async getFileMeta(name: string): Promise<FileMeta> { 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, - // ); + } catch (e: any) { + if (e.message === "Not found") { + console.info("Fetching file meta from fallback for", name); + } else { + console.warn( + `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 }; + return { ...meta, noSync: true }; } catch (fallbackError) { console.error( "Error during getFileMeta fallback", diff --git a/common/types.ts b/common/types.ts index cf54e60..404e778 100644 --- a/common/types.ts +++ b/common/types.ts @@ -6,5 +6,5 @@ export type FileMeta = { contentType: string; size: number; perm: "ro" | "rw"; - neverSync?: boolean; + noSync?: boolean; } & Record<string, any>; diff --git a/plug-api/lib/resolve.ts b/plug-api/lib/resolve.ts index 74bab1f..a4dfd6a 100644 --- a/plug-api/lib/resolve.ts +++ b/plug-api/lib/resolve.ts @@ -3,7 +3,7 @@ export function resolvePath( pathToResolve: string, fullUrl = false, ): string { - if (currentPage.startsWith("!") && !pathToResolve.startsWith("!")) { + if (isFederationPath(currentPage) && !isFederationPath(pathToResolve)) { let domainPart = currentPage.split("/")[0]; if (fullUrl) { domainPart = domainPart.substring(1); @@ -18,3 +18,7 @@ export function resolvePath( return pathToResolve; } } + +export function isFederationPath(path: string) { + return path.startsWith("!"); +} diff --git a/plugs/directive/command.ts b/plugs/directive/command.ts index 45e3de1..c76c009 100644 --- a/plugs/directive/command.ts +++ b/plugs/directive/command.ts @@ -1,4 +1,4 @@ -import { editor, markdown, space } from "$sb/silverbullet-syscall/mod.ts"; +import { editor, markdown, space, sync } from "$sb/silverbullet-syscall/mod.ts"; import { removeParentPointers, renderToText, @@ -7,15 +7,29 @@ import { import { renderDirectives } from "./directives.ts"; import { extractFrontmatter } from "$sb/lib/frontmatter.ts"; import { PageMeta } from "../../web/types.ts"; +import { isFederationPath } from "$sb/lib/resolve.ts"; export async function updateDirectivesOnPageCommand() { // If `arg` is a string, it's triggered automatically via an event, not explicitly via a command - const pageMeta = await space.getPageMeta(await editor.getCurrentPage()); + const currentPage = await editor.getCurrentPage(); + const pageMeta = await space.getPageMeta(currentPage); const text = await editor.getText(); const tree = await markdown.parseMarkdown(text); const metaData = await extractFrontmatter(tree, ["$disableDirectives"]); + + if (isFederationPath(currentPage)) { + console.info("Current page is a federation page, not updating directives."); + } + if (metaData.$disableDirectives) { - // Not updating, directives disabled + console.info("Directives disabled in page meta, not updating them."); + return; + } + + if (!(await sync.hasInitialSyncCompleted())) { + console.info( + "Initial sync hasn't completed yet, not updating directives.", + ); return; } @@ -96,7 +110,6 @@ export async function updateDirectivesOnPageCommand() { } } -// Pure server driven implementation of directive updating export async function updateDirectives( pageMeta: PageMeta, text: string, diff --git a/plugs/directive/directives.ts b/plugs/directive/directives.ts index 8633bd8..4dd3b8e 100644 --- a/plugs/directive/directives.ts +++ b/plugs/directive/directives.ts @@ -35,14 +35,6 @@ export async function directiveDispatcher( const directiveStartText = renderToText(directiveStart).trim(); const directiveEndText = renderToText(directiveEnd).trim(); - if (!(await sync.hasInitialSyncCompleted())) { - console.info( - "Initial sync hasn't completed yet, not updating directives.", - ); - // Render the query directive as-is - return renderToText(directiveTree); - } - if (directiveStart.children!.length === 1) { // Everything not #query const match = directiveStartRegex.exec(directiveStart.children![0].text!); diff --git a/plugs/directive/template_directive.ts b/plugs/directive/template_directive.ts index ef3fecc..58c9284 100644 --- a/plugs/directive/template_directive.ts +++ b/plugs/directive/template_directive.ts @@ -1,5 +1,10 @@ import { queryRegex } from "$sb/lib/query.ts"; -import { ParseTree, renderToText } from "$sb/lib/tree.ts"; +import { + findNodeOfType, + ParseTree, + renderToText, + traverseTree, +} from "$sb/lib/tree.ts"; import { markdown, space } from "$sb/silverbullet-syscall/mod.ts"; import Handlebars from "handlebars"; @@ -9,6 +14,7 @@ import { directiveRegex } from "./directives.ts"; import { updateDirectives } from "./command.ts"; import { buildHandebarOptions } from "./util.ts"; import { PageMeta } from "../../web/types.ts"; +import { resolvePath } from "$sb/lib/resolve.ts"; const templateRegex = /\[\[([^\]]+)\]\]\s*(.*)\s*/; @@ -24,7 +30,7 @@ export async function templateDirectiveRenderer( if (!match) { throw new Error(`Invalid template directive: ${arg}`); } - const template = match[1]; + let templatePath = match[1]; const args = match[2]; let parsedArgs = {}; if (args) { @@ -39,20 +45,29 @@ export async function templateDirectiveRenderer( } } let templateText = ""; - if (template.startsWith("http://") || template.startsWith("https://")) { + if ( + templatePath.startsWith("http://") || templatePath.startsWith("https://") + ) { try { - const req = await fetch(template); + const req = await fetch(templatePath); templateText = await req.text(); } catch (e: any) { templateText = `ERROR: ${e.message}`; } } else { - templateText = await space.readPage(template); + templatePath = resolvePath(pageMeta.name, templatePath); + templateText = await space.readPage(templatePath); } const tree = await markdown.parseMarkdown(templateText); await extractFrontmatter(tree, [], true); // Remove entire frontmatter section, if any + + // Resolve paths in the template + rewritePageRefs(tree, templatePath); + let newBody = renderToText(tree); + // console.log("Rewritten template:", newBody); + // if it's a template injection (not a literal "include") if (directive === "use") { const templateFn = Handlebars.compile( @@ -67,6 +82,40 @@ export async function templateDirectiveRenderer( return newBody.trim(); } +function rewritePageRefs(tree: ParseTree, templatePath: string) { + traverseTree(tree, (n): boolean => { + if (n.type === "DirectiveStart") { + const pageRef = findNodeOfType(n, "PageRef")!; + if (pageRef) { + const pageRefName = pageRef.children![0].text!.slice(2, -2); + pageRef.children![0].text = `[[${ + resolvePath(templatePath, pageRefName) + }]]`; + } + const directiveText = n.children![0].text; + // #use or #import + if (directiveText) { + const match = /\[\[(.+)\]\]/.exec(directiveText); + if (match) { + const pageRefName = match[1]; + n.children![0].text = directiveText.replace( + match[0], + `[[${resolvePath(templatePath, pageRefName)}]]`, + ); + } + } + + return true; + } + if (n.type === "WikiLinkPage") { + n.children![0].text = resolvePath(templatePath, n.children![0].text!); + return true; + } + + return false; + }); +} + export function cleanTemplateInstantiations(text: string) { return text.replaceAll(directiveRegex, ( _fullMatch, diff --git a/web/sync_service.ts b/web/sync_service.ts index e5e9a9c..1dea4be 100644 --- a/web/sync_service.ts +++ b/web/sync_service.ts @@ -192,9 +192,9 @@ export class SyncService { let remoteHash: number | undefined; try { const localMeta = await this.localSpacePrimitives.getFileMeta(name); - if (localMeta.neverSync) { + if (localMeta.noSync) { console.info( - "File marked as neverSync, skipping sync in this cycle", + "File marked as no sync, skipping sync in this cycle", name, ); await this.registerSyncStop();