feat: getTiddlerHtml

This commit is contained in:
lin onetwo 2023-06-17 01:38:02 +08:00
parent 61892320f9
commit 232d0ce103
4 changed files with 81 additions and 41 deletions

View file

@ -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<IWorkspaceService>(serviceIdentifier.Workspace);
const authService = container.get<IAuthenticationService>(serviceIdentifier.Authentication);
const wikiService = container.get<IWikiService>(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<GlobalResponse> {
// 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) {

View file

@ -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<NAME extends 'deleteTiddler' | 'getFavicon' | 'getIndex' | 'getStatus' | 'getTiddler' | 'getTiddlersJSON' | 'putTiddler' | 'getFile'>(
callWikiIpcServerRoute<NAME extends IpcServerRouteNames>(
workspaceID: string,
route: NAME,
...arguments_: Parameters<IpcServerRouteMethods[NAME]>

View file

@ -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<IWikiServerRouteResponse> {
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 };
}
}
}

View file

@ -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),