diff --git a/localization/locales/en/translation.json b/localization/locales/en/translation.json
index 06d35329..b3ff82ee 100644
--- a/localization/locales/en/translation.json
+++ b/localization/locales/en/translation.json
@@ -54,7 +54,8 @@
"RestartServiceComplete": "Restart Service Complete",
"SyncNow": "Sync To Cloud",
"NoNetworkConnection": "No Network Connection",
- "OpenCommandPalette": "Open CommandPalette"
+ "OpenCommandPalette": "Open CommandPalette",
+ "BackupNow": "Git Backup Locally"
},
"Updater": {
"CheckingFailed": "Checking Failed (Network Error)",
@@ -158,11 +159,13 @@
"Port": "Local host server port",
"PathDescription": "Location of your local wiki folder.",
"SyncOnInterval": "Sync On Interval",
- "SyncOnIntervalDescription": "If turned off, there is only one automatic sync when the application is opened, and one manual sync when the user triggers it by clicking the sync button in the wiki. When on, it will automatically sync according to the time interval in the global settings (off by default to save CPU usage from file change listening), and will still automatically sync on startup, or manually by clicking the button.",
+ "SyncOnIntervalDescription": "When on, it will automatically sync according to the time interval in the global settings, and will still automatically sync on startup, or manually by clicking the button. Will auto backup data to local git before sync. If turned off, there is only one automatic sync when the application is opened, and one manual sync when the user triggers it by clicking the sync button in the wiki. ",
"SyncOnStartup": "Sync On App Start",
"SyncOnStartupDescription": "Commit and Sync once the app cold start.",
"Name": "Workspace Name",
- "NameDescription": "The name of the workspace, which will be displayed on the sidebar, can be different from the actual folder name of the Git repository in the workspace"
+ "NameDescription": "The name of the workspace, which will be displayed on the sidebar, can be different from the actual folder name of the Git repository in the workspace",
+ "BackupOnInterval": "Backup On Interval",
+ "BackupOnIntervalDescription": "When enabled, data will be automatically backed up with local Git at regular intervals (interval in global settings), so that even if no cloud git synchronization address is configured, it will be automatically backed up locally."
},
"Dialog": {
"CantFindWorkspaceFolderRemoveWorkspace": "Cannot find the workspace folder that was still there before! \nThe folders that should have existed here may have been removed, or there is no wiki in this folder! \nDo you want to remove the workspace?",
@@ -226,8 +229,8 @@
"ClearBrowsingData": "Clear Browsing Data (git isn't affected)",
"General": "UI & Interact",
"Sync": "Sync",
- "SyncInterval": "Sync Interval",
- "SyncIntervalDescription": "After there is no new change for more than this length of time, it will automatically start backing up to Github (take effect after restart app)",
+ "SyncInterval": "Sync/Backup Interval",
+ "SyncIntervalDescription": "After this length of time, it will automatically start backing up to Github, if is a local workspace it will create a local git backup (take effect after restart app)",
"DefaultUserName": "User Name",
"DefaultUserNameDetail": "The user name in the Wiki, this only take effect after restart, this will fill in the creator field of the newly created or edited tiddlers. Can be override by user name set in the workspace setting.",
"ShowSideBarDetail": "Sidebar lets you switch easily between workspaces.",
diff --git a/localization/locales/zh_CN/translation.json b/localization/locales/zh_CN/translation.json
index 19985965..3e097d1c 100644
--- a/localization/locales/zh_CN/translation.json
+++ b/localization/locales/zh_CN/translation.json
@@ -55,6 +55,7 @@
"RestartService": "重启服务",
"RestartServiceComplete": "重启服务成功",
"SyncNow": "立即同步云端",
+ "BackupNow": "立即本地Git备份",
"NoNetworkConnection": "无网络连接"
},
"Updater": {
@@ -189,8 +190,10 @@
"URL": "本地服务器地址",
"Port": "本地服务器端口",
"PathDescription": "本地维基文件夹的地址",
- "SyncOnInterval": "定时自动同步",
- "SyncOnIntervalDescription": "如果关闭,则只有在应用程序打开时会有一次自动同步,还有当用户通过点击维基中的同步按钮手动触发同步。开启后会根据全局设置里的时间间隔自动同步(默认关闭以节省文件变动监听带来的CPU占用),并且依然会在启动时自动同步,点击按钮也可以手动同步。",
+ "SyncOnInterval": "定时自动同步备份",
+ "SyncOnIntervalDescription": "开启后会根据全局设置里的时间间隔自动同步,并且依然会在启动时自动同步,点击按钮也可以手动同步。同步云端前会自动先把数据备份到本地Git。如果关闭,则只有在应用程序打开时会有一次自动同步,还有当用户通过点击维基中的同步按钮手动触发同步。",
+ "BackupOnInterval": "定时自动备份",
+ "BackupOnIntervalDescription": "开启时,每隔一段时间(全局设置里的时间间隔)会自动用本地Git备份数据一次,这样即使没有配置云端同步地址,也会自动备份到本地。",
"SyncOnStartup": "启动时自动同步",
"SyncOnStartupDescription": "在应用冷启动时自动同步一次。",
"Name": "工作区名",
@@ -264,8 +267,8 @@
"Token": "Git身份凭证",
"TokenDescription": "用于向Git服务器验证身份并同步内容的凭证,可通过登录在线存储服务(如Github)来取得,也可以手动获取「Personal Access Token」后填到这里。",
"Sync": "同步",
- "SyncInterval": "同步间隔",
- "SyncIntervalDescription": "超过这段长度的时间没有新的改动后,就会自动开始备份到 Github(重启后生效)",
+ "SyncInterval": "同步/备份间隔",
+ "SyncIntervalDescription": "每经过这段长度的时间后,就会自动开始备份到 Github,如果工作区是本地工作区则会创建本地备份(重启后生效)",
"General": "界面和交互",
"ShowSideBarDetail": "侧边栏让你可以在工作区之间快速切换",
"ShowSideBarShortcut": "展示侧边栏的快捷方式",
diff --git a/src/pages/AddWorkspace/useForm.ts b/src/pages/AddWorkspace/useForm.ts
index 0e112f2d..164351b6 100644
--- a/src/pages/AddWorkspace/useForm.ts
+++ b/src/pages/AddWorkspace/useForm.ts
@@ -147,5 +147,6 @@ export function workspaceConfigFromForm(form: IWikiWorkspaceForm, isCreateMainWo
tagName: !isCreateMainWorkspace ? form.tagName : null,
port: form.wikiPort,
wikiFolderLocation: form.wikiFolderLocation,
+ backupOnInterval: true,
};
}
diff --git a/src/pages/EditWorkspace/index.tsx b/src/pages/EditWorkspace/index.tsx
index da7fede4..c1caec3e 100644
--- a/src/pages/EditWorkspace/index.tsx
+++ b/src/pages/EditWorkspace/index.tsx
@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable unicorn/no-useless-undefined */
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable unicorn/no-null */
@@ -145,6 +146,7 @@ export default function EditWorkspace(): JSX.Element {
const originalWorkspace = useWorkspaceObservable(workspaceID);
const [workspace, workspaceSetter, onSave] = useForm(originalWorkspace);
const {
+ backupOnInterval,
disableAudio,
disableNotifications,
gitUrl,
@@ -322,31 +324,52 @@ export default function EditWorkspace(): JSX.Element {
/>
)}
{storageService !== SupportedStorageServices.local && (
-
+ <>
-
-
-
- workspaceSetter({ ...workspace, syncOnInterval: event.target.checked })}
- />
-
-
-
-
-
- workspaceSetter({ ...workspace, syncOnStartup: event.target.checked })}
- />
-
-
-
+
+
+
+
+ workspaceSetter({ ...workspace, syncOnInterval: event.target.checked })}
+ />
+
+
+
+
+
+ workspaceSetter({ ...workspace, syncOnStartup: event.target.checked })}
+ />
+
+
+
+ >
+ )}
+ {storageService === SupportedStorageServices.local && (
+ <>
+
+
+
+
+
+
+ workspaceSetter({ ...workspace, backupOnInterval: event.target.checked })}
+ />
+
+
+
+ >
)}
{!isSubWiki && (
diff --git a/src/services/git/gitWorker.ts b/src/services/git/gitWorker.ts
index a01df889..b14e8229 100644
--- a/src/services/git/gitWorker.ts
+++ b/src/services/git/gitWorker.ts
@@ -3,7 +3,7 @@ import 'source-map-support/register';
import { expose } from 'threads/worker';
import { Observable } from 'rxjs';
import { clone, commitAndSync, GitStep, ILoggerContext, initGit, getModifiedFileList, getRemoteUrl, SyncParameterMissingError } from 'git-sync-js';
-import { IGitLogMessage, IGitUserInfos } from './interface';
+import type { ICommitAndSyncConfigs, IGitLogMessage, IGitUserInfos } from './interface';
import { defaultGitInfo } from './defaultGitInfo';
import { WikiChannel } from '@/constants/channels';
import type { IWorkspace } from '@services/workspaces/interface';
@@ -64,12 +64,11 @@ function initWikiGit(wikiFolderPath: string, syncImmediately?: boolean, remoteUr
* @param {string} remoteUrl
* @param {{ login: string, email: string, accessToken: string }} userInfo
*/
-function commitAndSyncWiki(workspace: IWorkspace, remoteUrl: string, userInfo: IGitUserInfos): Observable {
+function commitAndSyncWiki(workspace: IWorkspace, configs: ICommitAndSyncConfigs): Observable {
return new Observable((observer) => {
void commitAndSync({
dir: workspace.wikiFolderLocation,
- remoteUrl,
- userInfo,
+ ...configs,
defaultGitInfo,
logger: {
debug: (message: string, context: ILoggerContext): unknown =>
diff --git a/src/services/git/index.ts b/src/services/git/index.ts
index 8b94f214..e995be0c 100644
--- a/src/services/git/index.ts
+++ b/src/services/git/index.ts
@@ -24,7 +24,7 @@ import type { IAuthenticationService, ServiceBranchTypes } from '@services/auth/
import type { IWikiService } from '@services/wiki/interface';
import { logger } from '@services/libs/log';
import i18n from '@services/libs/i18n';
-import { IGitLogMessage, IGitService, IGitUserInfos } from './interface';
+import { ICommitAndSyncConfigs, IGitLogMessage, IGitService, IGitUserInfos } from './interface';
import { WikiChannel } from '@/constants/channels';
import { GitWorker } from './gitWorker';
import { Observer } from 'rxjs';
@@ -272,13 +272,13 @@ export class Git implements IGitService {
});
}
- public async commitAndSync(workspace: IWorkspace, remoteUrl: string, userInfo: IGitUserInfos): Promise {
+ public async commitAndSync(workspace: IWorkspace, config: ICommitAndSyncConfigs): Promise {
if (!net.isOnline()) {
return;
}
try {
return await new Promise((resolve, reject) => {
- this.gitWorker?.commitAndSyncWiki(workspace, remoteUrl, userInfo).subscribe(this.getWorkerObserver(resolve, reject));
+ this.gitWorker?.commitAndSyncWiki(workspace, config).subscribe(this.getWorkerObserver(resolve, reject));
});
} catch (error) {
this.createFailedDialog((error as Error).message, workspace.wikiFolderLocation);
diff --git a/src/services/git/interface.ts b/src/services/git/interface.ts
index 1388e876..f52ead5a 100644
--- a/src/services/git/interface.ts
+++ b/src/services/git/interface.ts
@@ -22,13 +22,15 @@ export interface IGitLogMessage {
meta: unknown;
}
+export interface ICommitAndSyncConfigs { commitOnly?: boolean; remoteUrl?: string; userInfo?: IGitUserInfos }
+
/**
* System Preferences are not stored in storage but stored in macOS Preferences.
* It can be retrieved and changed using Electron APIs
*/
export interface IGitService {
clone(remoteUrl: string, repoFolderPath: string, userInfo: IGitUserInfos): Promise;
- commitAndSync(workspace: IWorkspace, remoteUrl: string, userInfo: IGitUserInfos): Promise;
+ commitAndSync(workspace: IWorkspace, config: ICommitAndSyncConfigs): Promise;
getModifiedFileList(wikiFolderPath: string): Promise;
/** Inspect git's remote url from folder's .git config, return undefined if there is no initialized git */
getWorkspacesRemote(wikiFolderPath: string): Promise;
diff --git a/src/services/wiki/index.ts b/src/services/wiki/index.ts
index 74e05254..69c68ad8 100644
--- a/src/services/wiki/index.ts
+++ b/src/services/wiki/index.ts
@@ -467,7 +467,7 @@ export class Wiki implements IWikiService {
* Simply do some check before calling `gitService.commitAndSync`
*/
private async syncWikiIfNeeded(workspace: IWorkspace): Promise {
- const { wikiFolderLocation, gitUrl: githubRepoUrl, storageService } = workspace;
+ const { gitUrl: githubRepoUrl, storageService } = workspace;
const userInfo = await this.authService.getStorageServiceUserInfo(storageService);
if (storageService !== SupportedStorageServices.local && typeof githubRepoUrl === 'string' && userInfo !== undefined) {
const syncOnlyWhenNoDraft = await this.preferenceService.get('syncOnlyWhenNoDraft');
@@ -478,7 +478,9 @@ export class Wiki implements IWikiService {
return;
}
}
- await this.gitService.commitAndSync(workspace, githubRepoUrl, userInfo);
+ await this.gitService.commitAndSync(workspace, { remoteUrl: githubRepoUrl, userInfo });
+ } else if (workspace.backupOnInterval) {
+ await this.gitService.commitAndSync(workspace, { commitOnly: true });
}
}
@@ -491,8 +493,8 @@ export class Wiki implements IWikiService {
* Trigger git sync interval if needed in config
*/
private async startIntervalSyncIfNeeded(workspace: IWorkspace): Promise {
- const { syncOnInterval, wikiFolderLocation } = workspace;
- if (syncOnInterval) {
+ const { syncOnInterval, backupOnInterval, wikiFolderLocation } = workspace;
+ if (syncOnInterval || backupOnInterval) {
const syncDebounceInterval = await this.preferenceService.get('syncDebounceInterval');
this.wikiSyncIntervals[wikiFolderLocation] = setInterval(async () => {
await this.syncWikiIfNeeded(workspace);
diff --git a/src/services/wikiGitWorkspace/index.ts b/src/services/wikiGitWorkspace/index.ts
index 3076a4de..1fd844b2 100644
--- a/src/services/wikiGitWorkspace/index.ts
+++ b/src/services/wikiGitWorkspace/index.ts
@@ -45,7 +45,7 @@ export class WikiGitWorkspace implements IWikiGitWorkspaceService {
...workspacesToSync.map(async (workspace) => {
const userInfo = await this.authService.getStorageServiceUserInfo(workspace.storageService);
if (userInfo !== undefined && workspace.gitUrl !== null) {
- await this.gitService.commitAndSync(workspace, workspace.gitUrl, userInfo);
+ await this.gitService.commitAndSync(workspace, { remoteUrl: workspace.gitUrl, userInfo });
}
}),
]);
diff --git a/src/services/wikiGitWorkspace/interface.ts b/src/services/wikiGitWorkspace/interface.ts
index 8d88b455..3a711e12 100644
--- a/src/services/wikiGitWorkspace/interface.ts
+++ b/src/services/wikiGitWorkspace/interface.ts
@@ -9,7 +9,9 @@ import { IGitUserInfos } from '@services/git/interface';
export interface IWikiGitWorkspaceService {
/** Create a new workspace, and call git.initWikiGit , and rollback (delete created wiki folder) if it failed */
initWikiGitTransaction(newWorkspaceConfig: INewWorkspaceConfig, userInfo?: IGitUserInfos): Promise;
- /** register this in main.ts if syncBeforeShutdown in preference is true */
+ /** register this in main.ts if syncBeforeShutdown in preference is true
+ * If this is not an online sync wiki, there is no need to backup locally, because this feature is intended to sync between devices.
+ */
registerSyncBeforeShutdown(): void;
removeWorkspace: (id: string) => Promise;
}
diff --git a/src/services/workspaces/getWorkspaceMenuTemplate.ts b/src/services/workspaces/getWorkspaceMenuTemplate.ts
index f87f14a4..3830036d 100644
--- a/src/services/workspaces/getWorkspaceMenuTemplate.ts
+++ b/src/services/workspaces/getWorkspaceMenuTemplate.ts
@@ -108,11 +108,18 @@ export async function getWorkspaceMenuTemplate(
label: t('ContextMenu.SyncNow') + (isOnline ? '' : `(${t('ContextMenu.NoNetworkConnection')})`),
enabled: isOnline,
click: async () => {
- await service.git.commitAndSync(workspace, gitUrl, userInfo);
+ await service.git.commitAndSync(workspace, { remoteUrl: gitUrl, userInfo });
await service.workspaceView.restartWorkspaceViewService(id);
await service.view.reloadViewsWebContents(id);
},
});
+ } else {
+ template.push({
+ label: t('ContextMenu.BackupNow'),
+ click: async () => {
+ await service.git.commitAndSync(workspace, { commitOnly: true });
+ },
+ });
}
}
diff --git a/src/services/workspaces/index.ts b/src/services/workspaces/index.ts
index 0013f0d6..e22a5dfe 100644
--- a/src/services/workspaces/index.ts
+++ b/src/services/workspaces/index.ts
@@ -70,6 +70,7 @@ export class Workspace implements IWorkspaceService {
}
private async registerMenu(): Promise {
+ /* eslint-disable @typescript-eslint/no-misused-promises */
await this.menuService.insertMenu('Workspaces', [
{
label: () => i18n.t('Menu.SelectNextWorkspace'),
@@ -163,6 +164,7 @@ export class Workspace implements IWorkspaceService {
},
},
]);
+ /* eslint-enable @typescript-eslint/no-misused-promises */
await this.menuService.insertMenu('Workspaces', newMenuItems, undefined, undefined, 'updateWorkspaceMenuItems');
}
@@ -258,6 +260,7 @@ export class Workspace implements IWorkspaceService {
private sanitizeWorkspace(workspaceToSanitize: IWorkspace): IWorkspace {
const defaultValues: Partial = {
storageService: SupportedStorageServices.github,
+ backupOnInterval: true,
};
const fixingValues: Partial = {};
// we add mainWikiID in creation, we fix this value for old existed workspaces
diff --git a/src/services/workspaces/interface.ts b/src/services/workspaces/interface.ts
index 54e3f04e..ae0bbc64 100644
--- a/src/services/workspaces/interface.ts
+++ b/src/services/workspaces/interface.ts
@@ -9,6 +9,10 @@ export interface IWorkspace {
* Is this workspace selected by user, and showing corresponding webview?
*/
active: boolean;
+ /**
+ * When this workspace is a local workspace, we can still use local git to backup
+ */
+ backupOnInterval: boolean;
disableAudio: boolean;
disableNotifications: boolean;
/**
diff --git a/src/services/workspacesView/index.ts b/src/services/workspacesView/index.ts
index ce16fcd1..1ce68968 100644
--- a/src/services/workspacesView/index.ts
+++ b/src/services/workspacesView/index.ts
@@ -113,7 +113,7 @@ export class WorkspaceView implements IWorkspaceViewService {
throw new TypeError(`userInfo is undefined in initializeAllWorkspaceView when init ${wikiFolderLocation}`);
}
// sync in non-blocking way
- void this.gitService.commitAndSync(workspace, githubRepoUrl, userInfo);
+ void this.gitService.commitAndSync(workspace, { remoteUrl: githubRepoUrl, userInfo });
}
} catch (error) {
logger.error(`Can't sync at wikiStartup(), ${(error as Error).message}\n${(error as Error).stack ?? 'no stack'}`);