From 232d0ce103b1aef12e5dc8dccae4920cdf3998ce Mon Sep 17 00:00:00 2001 From: lin onetwo Date: Sat, 17 Jun 2023 01:38:02 +0800 Subject: [PATCH] feat: getTiddlerHtml --- .../view/setupIpcServerRoutesHandlers.ts | 78 +++++++++++-------- src/services/wiki/interface.ts | 4 +- src/services/wiki/ipcServerRoutes.ts | 39 ++++++++-- src/services/wiki/wikiWorker.ts | 1 + 4 files changed, 81 insertions(+), 41 deletions(-) diff --git a/src/services/view/setupIpcServerRoutesHandlers.ts b/src/services/view/setupIpcServerRoutesHandlers.ts index b1123fe7..355b83fd 100644 --- a/src/services/view/setupIpcServerRoutesHandlers.ts +++ b/src/services/view/setupIpcServerRoutesHandlers.ts @@ -5,12 +5,11 @@ import { container } from '@services/container'; import { logger } from '@services/libs/log'; import serviceIdentifier from '@services/serviceIdentifier'; import { IWikiService } from '@services/wiki/interface'; -import { IWikiServerRouteResponse } from '@services/wiki/ipcServerRoutes'; import { IWorkspaceService } from '@services/workspaces/interface'; import type { ITiddlerFields } from 'tiddlywiki'; export async function setupIpcServerRoutesHandlers(view: BrowserView, workspaceID: string) { - const urlBase = `tidgi://${workspaceID}/`; + const urlBase = `tidgi://${workspaceID}`; const workspaceService = container.get(serviceIdentifier.Workspace); const authService = container.get(serviceIdentifier.Authentication); const wikiService = container.get(serviceIdentifier.Wiki); @@ -18,73 +17,88 @@ export async function setupIpcServerRoutesHandlers(view: BrowserView, workspaceI { method: 'DELETE', path: /^\/bags\/default\/tiddlers\/(.+)$/, - handler: async (title: string) => await wikiService.callWikiIpcServerRoute(workspaceID, 'deleteTiddler', title), + name: 'deleteTiddler', + handler: async (_request: GlobalRequest, parameters: RegExpMatchArray | null) => + await wikiService.callWikiIpcServerRoute(workspaceID, 'deleteTiddler', parameters?.[1] ?? ''), }, { method: 'GET', path: /^\/favicon.ico$/, - handler: async () => await wikiService.callWikiIpcServerRoute(workspaceID, 'getFavicon'), + name: 'getFavicon', + handler: async (_request: GlobalRequest, _parameters: RegExpMatchArray | null) => await wikiService.callWikiIpcServerRoute(workspaceID, 'getFavicon'), }, { method: 'GET', path: /^\/files\/(.+)$/, - handler: async (fileName: string) => await wikiService.callWikiIpcServerRoute(workspaceID, 'getFile', fileName), + name: 'getFile', + handler: async (_request: GlobalRequest, parameters: RegExpMatchArray | null) => await wikiService.callWikiIpcServerRoute(workspaceID, 'getFile', parameters?.[1] ?? ''), }, { method: 'GET', path: /^\/$/, - handler: async () => await wikiService.callWikiIpcServerRoute(workspaceID, 'getIndex', (await workspaceService.get(workspaceID))?.rootTiddler ?? '$:/core/save/lazy-images'), + name: 'getIndex', + handler: async (_request: GlobalRequest, _parameters: RegExpMatchArray | null) => + await wikiService.callWikiIpcServerRoute(workspaceID, 'getIndex', (await workspaceService.get(workspaceID))?.rootTiddler ?? '$:/core/save/lazy-images'), }, { method: 'GET', path: /^\/status$/, - handler: async () => { + name: 'getStatus', + handler: async (_request: GlobalRequest, _parameters: RegExpMatchArray | null) => { const workspace = await workspaceService.get(workspaceID); const userName = workspace === undefined ? '' : await authService.getUserName(workspace); await wikiService.callWikiIpcServerRoute(workspaceID, 'getStatus', userName); }, }, + { + method: 'GET', + path: /^\/([^/]+)$/, + name: 'getTiddlerHtml', + handler: async (_request: GlobalRequest, parameters: RegExpMatchArray | null) => + await wikiService.callWikiIpcServerRoute(workspaceID, 'getTiddlerHtml', parameters?.[1] ?? ''), + }, { method: 'GET', path: /^\/recipes\/default\/tiddlers\/(.+)$/, - handler: async (title: string) => await wikiService.callWikiIpcServerRoute(workspaceID, 'getTiddler', title), + name: 'getTiddler', + handler: async (_request: GlobalRequest, parameters: RegExpMatchArray | null) => await wikiService.callWikiIpcServerRoute(workspaceID, 'getTiddler', parameters?.[1] ?? ''), + }, + { + method: 'GET', + path: /^\/recipes\/default\/tiddlers\/(.+)$/, + name: 'getTiddlersJSON', + handler: async (request: GlobalRequest, _parameters: RegExpMatchArray | null) => + await wikiService.callWikiIpcServerRoute(workspaceID, 'getTiddlersJSON', new URL(request.url).searchParams.get('filter') ?? ''), }, { method: 'PUT', path: /^\/recipes\/default\/tiddlers\/(.+)$/, - handler: async (title: string, fields: ITiddlerFields) => await wikiService.callWikiIpcServerRoute(workspaceID, 'putTiddler', title, fields), + name: 'putTiddler', + handler: async (request: GlobalRequest, parameters: RegExpMatchArray | null) => { + const body = await request.json() as ITiddlerFields; + await wikiService.callWikiIpcServerRoute(workspaceID, 'putTiddler', parameters?.[1] ?? '', body); + }, }, ]; async function handlerCallback(request: GlobalRequest): Promise { - // Extracting methods and URLs from requests - const { method, url } = request; - const urlPath = url.replace(urlBase, ''); - // DEBUG: console urlPath - console.log(`urlPath`, urlPath); - + const parsedUrl = new URL(request.url); // Iterate through methods to find matching routes for (const route of methods) { - if (method === route.method && route.path.test(urlPath)) { + if (request.method === route.method && route.path.test(parsedUrl.pathname)) { // Get the parameters in the URL path - const parameters = url.match(route.path); - + const parameters = parsedUrl.pathname.match(route.path); + logger.debug(`loadHTMLStringForView: ${route.name}`, { parsedUrl, parameters }); // Call the handler of the route to process the request and return the result - if (parameters === null) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error - // @ts-ignore Expected 2 arguments, but got 0. - const responseData: IWikiServerRouteResponse = await route.handler(); - return new Response(responseData.data, { status: responseData.statusCode, headers: responseData.headers }); - } else { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error - // @ts-ignore Expected 2 arguments, but got 0. - const responseData: IWikiServerRouteResponse = await route.handler(...parameters.slice(1)); - return new Response(responseData.data, { status: responseData.statusCode, headers: responseData.headers }); + const responseData = await route.handler(request, parameters); + if (responseData === undefined) { + const statusText = `loadHTMLStringForView: responseData is undefined ${request.url}`; + logger.warn(statusText); + return new Response(undefined, { status: 404, statusText }); } + return new Response(responseData.data, { status: responseData.statusCode, headers: responseData.headers }); } } - - // 如果没有找到匹配的路由,返回404错误 - const statusText = `loadHTMLStringForView: tidgi protocol is not handled ${url}`; + const statusText = `loadHTMLStringForView: tidgi protocol is not handled ${request.url}`; logger.warn(statusText); return new Response(undefined, { status: 404, statusText }); } @@ -95,7 +109,7 @@ export async function setupIpcServerRoutesHandlers(view: BrowserView, workspaceI if (!handled) { logger.warn(`loadHTMLStringForView: tidgi protocol is not handled`); } - await view.webContents.loadURL(urlBase); + await view.webContents.loadURL(`${urlBase}/`); // view.webContents.session.protocol.unhandle(`tidgi`); view.webContents.openDevTools({ mode: 'detach' }); } catch (error) { diff --git a/src/services/wiki/interface.ts b/src/services/wiki/interface.ts index 365ec6aa..e61f6f3b 100644 --- a/src/services/wiki/interface.ts +++ b/src/services/wiki/interface.ts @@ -6,7 +6,7 @@ import { ModuleThread } from 'threads'; import { IWikiServerRouteResponse } from './ipcServerRoutes'; import type { ISubWikiPluginContent } from './plugin/subWikiPlugin'; import { IWikiOperations } from './wikiOperations'; -import type { IpcServerRouteMethods, WikiWorker } from './wikiWorker'; +import type { IpcServerRouteMethods, IpcServerRouteNames, WikiWorker } from './wikiWorker'; /** * Handle wiki worker startup and restart @@ -16,7 +16,7 @@ export interface IWikiService { * Call wiki worker route methods, and return response. * Methods are copy from core/modules/server/routes , to support the IPC communication between renderer's browserView and main process and wiki worker. */ - callWikiIpcServerRoute( + callWikiIpcServerRoute( workspaceID: string, route: NAME, ...arguments_: Parameters diff --git a/src/services/wiki/ipcServerRoutes.ts b/src/services/wiki/ipcServerRoutes.ts index f298bd31..e2b60ad0 100644 --- a/src/services/wiki/ipcServerRoutes.ts +++ b/src/services/wiki/ipcServerRoutes.ts @@ -62,7 +62,7 @@ export class IpcServerRoutes { try { const data = await fs.readFile(filename); // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - const type = this.wikiInstance.config.fileExtensionInfo[extension] ? $tw.config.fileExtensionInfo[extension].type : 'application/octet-stream'; + const type = this.wikiInstance.config.fileExtensionInfo[extension] ? this.wikiInstance.config.fileExtensionInfo[extension].type : 'application/octet-stream'; return ({ statusCode: 200, headers: { 'Content-Type': type }, data }); } catch (error) { return { statusCode: 404, headers: { 'Content-Type': 'text/plain' }, data: `Error accessing file ${suppliedFilename} with error: ${(error as Error).toString()}` }; @@ -85,7 +85,7 @@ export class IpcServerRoutes { space: { recipe: 'default', }, - tiddlywiki_version: $tw.version, + tiddlywiki_version: this.wikiInstance.version, }); return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, data: text }; } @@ -110,8 +110,9 @@ export class IpcServerRoutes { 'type', 'uri', ]); - // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions - if (tiddler) { + if (tiddler === undefined) { + return { statusCode: 404, headers: { 'Content-Type': 'text/plain' }, data: `Tiddler "${title}" not exist` }; + } else { Object.keys(tiddler.fields).forEach((name) => { const value = tiddler.getFieldStrings(name); if (knownFields.has(name)) { @@ -127,8 +128,6 @@ export class IpcServerRoutes { tiddlerFields.bag = 'default'; tiddlerFields.type = tiddlerFields.type ?? 'text/vnd.tiddlywiki'; return { statusCode: 200, headers: { 'Content-Type': 'application/json; charset=utf8' }, data: JSON.stringify(tiddlerFields) }; - } else { - return { statusCode: 404, headers: { 'Content-Type': 'text/plain' }, data: `Tiddler "${title}" not exist` }; } } @@ -177,8 +176,34 @@ export class IpcServerRoutes { tiddlerFieldsToPut.text = tiddler.fields.text; } } - this.wikiInstance.wiki.addTiddler(new $tw.Tiddler(fields, { title })); + this.wikiInstance.wiki.addTiddler(new this.wikiInstance.Tiddler(fields, { title })); const changeCount = this.wikiInstance.wiki.getChangeCount(title).toString(); return { statusCode: 204, headers: { 'Content-Type': 'text/plain', Etag: `"default/${encodeURIComponent(title)}/${changeCount}:"` }, data: 'OK' }; } + + async getTiddlerHtml(title: string): Promise { + await this.waitForIpcServerRoutesAvailable(); + const tiddler = this.wikiInstance.wiki.getTiddler(title); + if (tiddler === undefined) { + return { statusCode: 404, headers: { 'Content-Type': 'text/plain' }, data: `Tiddler "${title}" not exist` }; + } else { + let renderType: string = tiddler.getFieldString('_render_type'); + let renderTemplate: string = tiddler.getFieldString('_render_template'); + // Tiddler fields '_render_type' and '_render_template' overwrite + // system wide settings for render type and template + if (this.wikiInstance.wiki.isSystemTiddler(title)) { + renderType = renderType ?? this.wikiInstance.server.get('system-tiddler-render-type') ?? 'text/plain'; + renderTemplate = renderTemplate ?? this.wikiInstance.server.get('system-tiddler-render-template') ?? '$:/core/templates/wikified-tiddler'; + } else { + renderType = renderType ?? this.wikiInstance.server.get('tiddler-render-type') ?? 'text/html'; + renderTemplate = renderTemplate ?? this.wikiInstance.server.get('tiddler-render-template') ?? '$:/core/templates/server/static.tiddler.html'; + } + // DEBUG: console renderType + console.log(`renderType`, renderType, 'renderTemplate', renderTemplate); + const text = this.wikiInstance.wiki.renderTiddler(renderType, renderTemplate, { parseAsInline: true, variables: { currentTiddler: title } }); + + // Naughty not to set a content-type, but it's the easiest way to ensure the browser will see HTML pages as HTML, and accept plain text tiddlers as CSS or JS + return { statusCode: 200, headers: { 'Content-Type': '; charset=utf8' }, data: text }; + } + } } diff --git a/src/services/wiki/wikiWorker.ts b/src/services/wiki/wikiWorker.ts index 5c9eed36..cb333827 100644 --- a/src/services/wiki/wikiWorker.ts +++ b/src/services/wiki/wikiWorker.ts @@ -39,6 +39,7 @@ const ipcServerRoutesMethods = { getIndex: ipcServerRoutes.getIndex.bind(ipcServerRoutes), getStatus: ipcServerRoutes.getStatus.bind(ipcServerRoutes), getTiddler: ipcServerRoutes.getTiddler.bind(ipcServerRoutes), + getTiddlerHtml: ipcServerRoutes.getTiddlerHtml.bind(ipcServerRoutes), getTiddlersJSON: ipcServerRoutes.getTiddlersJSON.bind(ipcServerRoutes), putTiddler: ipcServerRoutes.putTiddler.bind(ipcServerRoutes), getFile: ipcServerRoutes.getFile.bind(ipcServerRoutes),