wip: make wiki channel connect to server side

This commit is contained in:
lin onetwo 2023-09-28 02:29:16 +08:00
parent 81ee6b2db1
commit 219ba38e57
3 changed files with 187 additions and 0 deletions

View file

@ -0,0 +1,85 @@
import { WikiChannel } from '@/constants/channels';
export const wikiOperations = {
[WikiChannel.setState]: (stateKey: string, content: string) => `
$tw.wiki.addTiddler({ title: '$:/state/${stateKey}', text: \`${content}\` });
`,
[WikiChannel.addTiddler]: (nonceReceived: number, title: string, text: string, extraMeta = '{}', optionsString = '{}') => {
const options = JSON.parse(optionsString) as { withDate?: boolean };
return `
const dateObject = {};
${
options.withDate === true
? `
const existedTiddler = $tw.wiki.getTiddler(\`${title}\`);
let created = existedTiddler?.fields?.created;
const modified = $tw.utils.stringifyDate(new Date());
if (!existedTiddler) {
created = $tw.utils.stringifyDate(new Date());
}
dateObject.created = created;
dateObject.modified = modified;
`
: ''
}
$tw.wiki.addTiddler({ title: \`${title}\`, text: \`${text}\`, ...${extraMeta}, ...dateObject });
`;
},
[WikiChannel.getTiddlerText]: (title: string) => `
$tw.wiki.getTiddlerText(\`${title}\`);
`,
[WikiChannel.runFilter]: (filter: string) => `
$tw.wiki.compileFilter(\`${filter}\`)()
`,
[WikiChannel.getTiddlersAsJson]: (filter: string) => `
$tw.wiki.filterTiddlers(\`${filter}\`).map(title => {
const tiddler = $tw.wiki.getTiddler(title);
return tiddler?.fields;
}).filter(item => item !== undefined)
`,
[WikiChannel.setTiddlerText]: (title: string, value: string) => `
$tw.wiki.setText(\`${title}\`, 'text', undefined, \`${value}\`);
`,
[WikiChannel.renderWikiText]: (content: string) => `
$tw.wiki.renderText("text/html", "text/vnd.tiddlywiki", \`${content.replaceAll('`', '\\`')}\`);
`,
[WikiChannel.syncProgress]: (message: string) => `
$tw.wiki.addTiddler({ title: '$:/state/notification/${WikiChannel.syncProgress}', text: \`${message}\` });
$tw.notifier.display('$:/state/notification/${WikiChannel.syncProgress}');
`,
[WikiChannel.generalNotification]: (message: string) => `
$tw.wiki.addTiddler({ title: \`$:/state/notification/${WikiChannel.generalNotification}\`, text: \`${message}\` });
$tw.notifier.display(\`$:/state/notification/${WikiChannel.generalNotification}\`);
`,
[WikiChannel.openTiddler]: (tiddlerName: string) => `
let trimmedTiddlerName = \`${tiddlerName.replaceAll('\n', '')}\`;
let currentHandlerWidget = $tw.rootWidget;
let handled = false;
while (currentHandlerWidget && !handled) {
const bubbled = currentHandlerWidget.dispatchEvent({ type: "tm-navigate", navigateTo: trimmedTiddlerName, param: trimmedTiddlerName });
handled = !bubbled;
currentHandlerWidget = currentHandlerWidget.children?.[0];
}
`,
[WikiChannel.sendActionMessage]: (actionMessage: string) => `
$tw.rootWidget.dispatchEvent({ type: \`${actionMessage}\` });
`,
[WikiChannel.deleteTiddler]: (title: string) => `
$tw.wiki.deleteTiddler(\`${title}\`);
`,
[WikiChannel.printTiddler]: (tiddlerName: string) => `
var page = (${printer.printTiddler.toString()})(\`${tiddlerName}\`);
page?.print?.();
page?.close?.();
`,
};

View file

@ -0,0 +1,35 @@
import { WikiChannel } from '@/constants/channels';
import { wikiOperations as common } from './common';
export const wikiOperations = {
...common
[WikiChannel.syncProgress]: (message: string) => `
$tw.wiki.addTiddler({ title: '$:/state/notification/${WikiChannel.syncProgress}', text: \`${message}\` });
$tw.notifier.display('$:/state/notification/${WikiChannel.syncProgress}');
`,
[WikiChannel.generalNotification]: (message: string) => `
$tw.wiki.addTiddler({ title: \`$:/state/notification/${WikiChannel.generalNotification}\`, text: \`${message}\` });
$tw.notifier.display(\`$:/state/notification/${WikiChannel.generalNotification}\`);
`,
[WikiChannel.openTiddler]: (tiddlerName: string) => `
let trimmedTiddlerName = \`${tiddlerName.replaceAll('\n', '')}\`;
let currentHandlerWidget = $tw.rootWidget;
let handled = false;
while (currentHandlerWidget && !handled) {
const bubbled = currentHandlerWidget.dispatchEvent({ type: "tm-navigate", navigateTo: trimmedTiddlerName, param: trimmedTiddlerName });
handled = !bubbled;
currentHandlerWidget = currentHandlerWidget.children?.[0];
}
`,
[WikiChannel.printTiddler]: async (tiddlerName: string) => {
const printer = await import('../../libs/printer');
return `
var page = (${printer.printTiddler.toString()})(\`${tiddlerName}\`);
page?.print?.();
page?.close?.();
`;
},
};

View file

@ -0,0 +1,67 @@
/* eslint-disable @typescript-eslint/promise-function-async */
/* eslint-disable no-new-func */
/* eslint-disable @typescript-eslint/no-implied-eval */
/**
* Run some wiki operations on server side, so it works even when the wiki browser view is not visible.
*/
import type { ITiddlyWiki } from 'tiddlywiki';
import { IWikiOperations } from '../wikiOperations';
export class IpcServerRoutes {
private wikiInstance!: ITiddlyWiki;
private readonly pendingIpcServerRoutesRequests: Array<(value: void | PromiseLike<void>) => void> = [];
setWikiInstance(wikiInstance: ITiddlyWiki) {
this.wikiInstance = wikiInstance;
this.pendingIpcServerRoutesRequests.forEach((resolve) => {
resolve();
});
}
private async waitForIpcServerRoutesAvailable() {
if (this.wikiInstance !== undefined) {
return;
}
await new Promise<void>((resolve) => {
this.pendingIpcServerRoutesRequests.push(resolve);
});
}
private executeTWJavaScriptWhenIdle(script: string): Promise<unknown> {
return new Promise((resolve, reject) => {
setTimeout(() => {
try {
const result = new Function(`const $tw = arguments[0];return ${script}`)(this.wikiInstance) as unknown;
resolve(result);
} catch (error) {
reject(error);
}
}, 1);
});
}
// ██████ ██████ ███████ ██████ █████ ████████ ██ ██████ ███ ██ ███████
// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
// ██ ██ ██████ █████ ██████ ███████ ██ ██ ██ ██ ██ ██ ██ ███████
// ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
// ██████ ██ ███████ ██ ██ ██ ██ ██ ██ ██████ ██ ████ ███████
public wikiOperation<OP extends keyof IWikiOperations, T = string[]>(
operationType: OP,
...arguments_: Parameters<IWikiOperations[OP]>
): undefined | ReturnType<IWikiOperations[OP]> {
if (typeof wikiOperations[operationType] !== 'function') {
throw new TypeError(`${operationType} gets no useful handler`);
}
if (!Array.isArray(arguments_)) {
// TODO: better type handling here
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/restrict-template-expressions
throw new TypeError(`${(arguments_ as any) ?? ''} (${typeof arguments_}) is not a good argument array for ${operationType}`);
}
// @ts-expect-error A spread argument must either have a tuple type or be passed to a rest parameter.ts(2556) this maybe a bug of ts... try remove this comment after upgrade ts. And the result become void is weird too.
// eslint-disable-next-line @typescript-eslint/no-confusing-void-expression
return wikiOperations[operationType]<T>(...arguments_) as unknown as ReturnType<IWikiOperations[OP]>;
}
}
export const ipcServerRoutes: IpcServerRoutes = new IpcServerRoutes();