diff --git a/localization/locales/en/translation.json b/localization/locales/en/translation.json
index a018b192..4b8a3572 100644
--- a/localization/locales/en/translation.json
+++ b/localization/locales/en/translation.json
@@ -40,7 +40,9 @@
"SearchWithGoogle": "Search With Google",
"Cut": "Cut",
"Copy": "Copy",
- "Paste": "Paste"
+ "Paste": "Paste",
+ "RestartService": "Restart Service",
+ "RestartServiceComplete": "Restart Service Complete"
},
"AddWorkspace": {
"MainPageTipWithoutSidebar": "<0>Click 0>Workspaces > Add Workspace<2> on the menu to start using TiddlyWiki!2>",
diff --git a/localization/locales/zh_CN/translation.json b/localization/locales/zh_CN/translation.json
index 0365f73a..79f10455 100644
--- a/localization/locales/zh_CN/translation.json
+++ b/localization/locales/zh_CN/translation.json
@@ -41,7 +41,9 @@
"CopyLink": "复制链接",
"OpenLinkInBrowser": "在浏览器中打开链接",
"Paste": "粘贴",
- "SearchWithGoogle": "用 Google 搜索"
+ "SearchWithGoogle": "用 Google 搜索",
+ "RestartService": "重启服务",
+ "RestartServiceComplete": "重启服务成功"
},
"Menu": {
"TiddlyGit": "太记",
diff --git a/src/constants/channels.ts b/src/constants/channels.ts
index 0a69d277..5e64fa05 100644
--- a/src/constants/channels.ts
+++ b/src/constants/channels.ts
@@ -47,6 +47,7 @@ export enum WikiChannel {
getTiddlerTextDone = 'wiki-get-tiddler-text-done',
/** show message inside tiddlywiki to show git sync progress */
syncProgress = 'wiki-sync-progress',
+ generalNotification = 'wiki-notification-tiddly-git',
/** used to show wiki creation messages in the TiddlyGit UI for user to read */
createProgress = 'wiki-create-progress',
openTiddler = 'wiki-open-tiddler',
diff --git a/src/pages/Main/SortableWorkspaceSelector.tsx b/src/pages/Main/SortableWorkspaceSelector.tsx
index e9d03da8..a13a411d 100644
--- a/src/pages/Main/SortableWorkspaceSelector.tsx
+++ b/src/pages/Main/SortableWorkspaceSelector.tsx
@@ -7,6 +7,7 @@ import WorkspaceSelector from './WorkspaceSelector';
import { IWorkspace } from '@services/workspaces/interface';
import defaultIcon from '@/images/default-icon.png';
+import { WikiChannel } from '@/constants/channels';
export interface ISortableItemProps {
index: number;
@@ -64,6 +65,17 @@ export function SortableWorkspaceSelector({ index, workspace, showSidebarShortcu
label: t('ContextMenu.Reload'),
click: async () => await window.service.view.reloadViewsWebContents(id),
},
+ {
+ label: t('ContextMenu.RestartService'),
+ click: async () => {
+ const workspaceToRestart = await window.service.workspace.get(id);
+ if (workspaceToRestart !== undefined) {
+ await window.service.wiki.restartWiki(workspaceToRestart);
+ await window.service.view.reloadViewsWebContents(id);
+ await window.service.wiki.wikiOperation(WikiChannel.generalNotification, [t('ContextMenu.RestartServiceComplete')]);
+ }
+ },
+ },
];
if (!active && !isSubWiki) {
diff --git a/src/preload/wikiOperation.ts b/src/preload/wikiOperation.ts
index 33ad233d..a4b20d45 100644
--- a/src/preload/wikiOperation.ts
+++ b/src/preload/wikiOperation.ts
@@ -31,6 +31,12 @@ ipcRenderer.on(WikiChannel.syncProgress, async (event, message: string) => {
$tw.notifier.display('$:/state/notification/${WikiChannel.syncProgress}');
`);
});
+ipcRenderer.on(WikiChannel.generalNotification, async (event, message: string) => {
+ await webFrame.executeJavaScript(`
+ $tw.wiki.addTiddler({ title: '$:/state/notification/${WikiChannel.generalNotification}', text: '${message}' });
+ $tw.notifier.display('$:/state/notification/${WikiChannel.generalNotification}');
+ `);
+});
// open a tiddler
ipcRenderer.on(WikiChannel.openTiddler, async (event, tiddlerName: string) => {
await webFrame.executeJavaScript(`
diff --git a/src/services/libs/log/rendererTransport.ts b/src/services/libs/log/rendererTransport.ts
index 5f8cf1e8..2f3a4e7d 100644
--- a/src/services/libs/log/rendererTransport.ts
+++ b/src/services/libs/log/rendererTransport.ts
@@ -1,31 +1,11 @@
/* eslint-disable global-require */
import Transport from 'winston-transport';
-import { container } from '@services/container';
-import type { IViewService } from '@services/view/interface';
-import type { IWindowService } from '@services/windows/interface';
-import serviceIdentifier from '@services/serviceIdentifier';
-import { WindowNames } from '@services/windows/WindowProperties';
-import { WikiChannel } from '@/constants/channels';
-
-const handlers = {
- [WikiChannel.createProgress]: (message: string) => {
- const windowService = container.get(serviceIdentifier.Window);
- const createWorkspaceWindow = windowService.get(WindowNames.addWorkspace);
- createWorkspaceWindow?.webContents?.send(WikiChannel.createProgress, message);
- },
- [WikiChannel.syncProgress]: async (message: string) => {
- const viewService = container.get(serviceIdentifier.View);
- const browserView = await viewService.getActiveBrowserView();
- browserView?.webContents?.send(WikiChannel.syncProgress, message);
- },
-};
-
-export type IHandlers = typeof handlers;
+import { IWikiOperations, wikiOperations } from '@services/wiki/wikiOperations';
export interface IInfo {
/** which method or handler function we are logging for */
- handler: keyof IHandlers;
+ handler: keyof IWikiOperations;
/** the detailed massage for debugging */
message: string;
}
@@ -40,8 +20,8 @@ export default class RendererTransport extends Transport {
});
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- if (info.handler && info.handler in handlers) {
- void handlers[info.handler](info.message);
+ if (info.handler && info.handler in wikiOperations) {
+ void wikiOperations[info.handler](info.message);
}
callback();
diff --git a/src/services/wiki/index.ts b/src/services/wiki/index.ts
index f5833517..b89964ca 100644
--- a/src/services/wiki/index.ts
+++ b/src/services/wiki/index.ts
@@ -33,6 +33,7 @@ import type { WikiWorker } from './wikiWorker';
// @ts-expect-error it don't want .ts
// eslint-disable-next-line import/no-webpack-loader-syntax
import workerURL from 'threads-plugin/dist/loader?name=wikiWorker!./wikiWorker.ts';
+import { IWikiOperations, wikiOperations } from './wikiOperations';
@injectable()
export class Wiki implements IWikiService {
@@ -379,8 +380,20 @@ export class Wiki implements IWikiService {
return this.justStartedWiki[wikiFolderLocation] ?? false;
}
+ /**
+ * Watch wiki change so we can trigger git sync
+ * Simply do some check before calling `this.watchWikiForDebounceCommitAndSync`
+ */
+ private async tryWatchForSync(workspace: IWorkspace, watchPath?: string): Promise {
+ const { wikiFolderLocation, gitUrl: githubRepoUrl, storageService } = workspace;
+ const userInfo = await this.authService.getStorageServiceUserInfo(storageService);
+ if (storageService !== SupportedStorageServices.local && typeof githubRepoUrl === 'string' && userInfo !== undefined) {
+ await this.watchWikiForDebounceCommitAndSync(wikiFolderLocation, githubRepoUrl, userInfo, watchPath);
+ }
+ }
+
public async wikiStartup(workspace: IWorkspace): Promise {
- const { wikiFolderLocation, gitUrl: githubRepoUrl, port, isSubWiki, mainWikiToLink, storageService } = workspace;
+ const { wikiFolderLocation, port, isSubWiki, mainWikiToLink } = workspace;
// remove $:/StoryList, otherwise it sometimes cause $__StoryList_1.tid to be generated
try {
@@ -389,21 +402,15 @@ export class Wiki implements IWikiService {
// do nothing
}
- const userInfo = await this.authService.getStorageServiceUserInfo(storageService);
// use workspace specific userName first, and fall back to preferences' userName, pass empty editor username if undefined
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const userName = (workspace.userName || (await this.authService.get('userName'))) ?? '';
- /** watch wiki change so we can trigger git sync */
- const tryWatchForSync = async (watchPath?: string): Promise => {
- if (storageService !== SupportedStorageServices.local && typeof githubRepoUrl === 'string' && userInfo !== undefined) {
- await this.watchWikiForDebounceCommitAndSync(wikiFolderLocation, githubRepoUrl, userInfo, watchPath);
- }
- };
+
// if is main wiki
if (!isSubWiki) {
await this.startWiki(wikiFolderLocation, port, userName);
// sync to cloud, do this in a non-blocking way
- void tryWatchForSync(path.join(wikiFolderLocation, TIDDLERS_PATH));
+ void this.tryWatchForSync(workspace, path.join(wikiFolderLocation, TIDDLERS_PATH));
} else {
// if is private repo wiki
// if we are creating a sub-wiki just now, restart the main wiki to load content from private wiki
@@ -412,17 +419,33 @@ export class Wiki implements IWikiService {
if (mainWorkspace === undefined) {
throw new Error(`mainWorkspace is undefined in wikiStartup() for mainWikiPath ${mainWikiToLink}`);
}
- await this.stopWatchWiki(mainWikiToLink);
- await this.stopWiki(mainWikiToLink);
- await this.startWiki(mainWikiToLink, mainWorkspace.port, userName);
- // sync main wiki to cloud, do this in a non-blocking way
- void tryWatchForSync(path.join(mainWikiToLink, TIDDLERS_PATH));
+ await this.restartWiki(mainWorkspace);
// sync self to cloud, subwiki's content is all in root folder path, do this in a non-blocking way
- void tryWatchForSync();
+ void this.tryWatchForSync(workspace);
}
}
}
+ public async restartWiki(workspace: IWorkspace): Promise {
+ const { wikiFolderLocation, port, userName: workspaceUserName, isSubWiki } = workspace;
+ // use workspace specific userName first, and fall back to preferences' userName, pass empty editor username if undefined
+ // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
+ const userName = (workspaceUserName || (await this.authService.get('userName'))) ?? '';
+
+ await this.stopWatchWiki(wikiFolderLocation);
+ if (!isSubWiki) {
+ await this.stopWiki(wikiFolderLocation);
+ await this.startWiki(wikiFolderLocation, port, userName);
+ }
+ if (isSubWiki) {
+ // sync sub wiki to cloud, do this in a non-blocking way
+ void this.tryWatchForSync(workspace, wikiFolderLocation);
+ } else {
+ // sync main wiki to cloud, do this in a non-blocking way
+ void this.tryWatchForSync(workspace, path.join(wikiFolderLocation, TIDDLERS_PATH));
+ }
+ }
+
// watch-wiki.ts
private readonly frequentlyChangedFileThatShouldBeIgnoredFromWatch = ['output', /\$__StoryList/];
private readonly topLevelFoldersToIgnored = ['node_modules', '.git'];
@@ -512,4 +535,19 @@ export class Wiki implements IWikiService {
public async updateSubWikiPluginContent(mainWikiPath: string, newConfig?: IWorkspace, oldConfig?: IWorkspace): Promise {
return updateSubWikiPluginContent(mainWikiPath, newConfig, oldConfig);
}
+
+ public wikiOperation(
+ operationType: OP,
+ arguments_: Parameters,
+ ): undefined | ReturnType {
+ 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}`);
+ }
+ return wikiOperations[operationType].apply(undefined, arguments_) as unknown as ReturnType;
+ }
}
diff --git a/src/services/wiki/interface.ts b/src/services/wiki/interface.ts
index 05312410..443270ea 100644
--- a/src/services/wiki/interface.ts
+++ b/src/services/wiki/interface.ts
@@ -3,6 +3,7 @@ import { WikiChannel } from '@/constants/channels';
import { IWorkspace } from '@services/workspaces/interface';
import { IGitUserInfos } from '@services/git/interface';
import type { ISubWikiPluginContent } from './plugin/subWikiPlugin';
+import { IWikiOperations } from './wikiOperations';
export type IWikiMessage = IWikiLogMessage | IWikiControlMessage;
export interface IWikiLogMessage {
@@ -30,6 +31,7 @@ export interface IWikiService {
/** call wiki worker to actually start nodejs wiki */
startWiki(homePath: string, tiddlyWikiPort: number, userName: string): Promise;
stopWiki(homePath: string): Promise;
+ restartWiki(workspace: IWorkspace): Promise;
stopAllWiki(): Promise;
copyWikiTemplate(newFolderPath: string, folderName: string): Promise;
getSubWikiPluginContent(mainWikiPath: string): Promise;
@@ -66,6 +68,7 @@ export interface IWikiService {
setWikiStartLockOn(wikiFolderLocation: string): void;
setAllWikiStartLockOff(): void;
checkWikiStartLock(wikiFolderLocation: string): boolean;
+ wikiOperation(operationType: OP, arguments_: Parameters): undefined | ReturnType;
}
export const WikiServiceIPCDescriptor = {
channel: WikiChannel.name,
@@ -73,6 +76,7 @@ export const WikiServiceIPCDescriptor = {
updateSubWikiPluginContent: ProxyPropertyType.Function,
startWiki: ProxyPropertyType.Function,
stopWiki: ProxyPropertyType.Function,
+ restartWiki: ProxyPropertyType.Function,
stopAllWiki: ProxyPropertyType.Function,
copyWikiTemplate: ProxyPropertyType.Function,
getSubWikiPluginContent: ProxyPropertyType.Function,
@@ -89,5 +93,6 @@ export const WikiServiceIPCDescriptor = {
watchWikiForDebounceCommitAndSync: ProxyPropertyType.Function,
stopWatchWiki: ProxyPropertyType.Function,
stopWatchAllWiki: ProxyPropertyType.Function,
+ wikiOperation: ProxyPropertyType.Function,
},
};
diff --git a/src/services/wiki/wikiOperations.ts b/src/services/wiki/wikiOperations.ts
new file mode 100644
index 00000000..a3d5449b
--- /dev/null
+++ b/src/services/wiki/wikiOperations.ts
@@ -0,0 +1,29 @@
+import { WikiChannel } from '@/constants/channels';
+import { container } from '@services/container';
+import serviceIdentifier from '@services/serviceIdentifier';
+import { IViewService } from '@services/view/interface';
+import { IWindowService } from '@services/windows/interface';
+import { WindowNames } from '@services/windows/WindowProperties';
+
+/**
+ * Handle sending message to trigger operations defined in `src/preload/wikiOperation.ts`
+ */
+export const wikiOperations = {
+ [WikiChannel.createProgress]: (message: string): void => {
+ const windowService = container.get(serviceIdentifier.Window);
+ const createWorkspaceWindow = windowService.get(WindowNames.addWorkspace);
+ createWorkspaceWindow?.webContents?.send(WikiChannel.createProgress, message);
+ },
+ [WikiChannel.syncProgress]: async (message: string): Promise => {
+ const viewService = container.get(serviceIdentifier.View);
+ const browserView = await viewService.getActiveBrowserView();
+ browserView?.webContents?.send(WikiChannel.syncProgress, message);
+ },
+ [WikiChannel.generalNotification]: async (message: string): Promise => {
+ const viewService = container.get(serviceIdentifier.View);
+ const browserView = await viewService.getActiveBrowserView();
+ browserView?.webContents?.send(WikiChannel.generalNotification, message);
+ },
+ // TODO: add more operations here from `src/preload/wikiOperation.ts`
+};
+export type IWikiOperations = typeof wikiOperations;