mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2025-12-05 18:20:39 -08:00
Compare commits
2 commits
d242d9a63c
...
8963527b41
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8963527b41 | ||
|
|
b4ebaa66df |
9 changed files with 354 additions and 11 deletions
128
features/stepDefinitions/sync.ts
Normal file
128
features/stepDefinitions/sync.ts
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import { Then, When } from '@cucumber/cucumber';
|
||||
import { exec as gitExec } from 'dugite';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { wikiTestRootPath } from '../supports/paths';
|
||||
import type { ApplicationWorld } from './application';
|
||||
|
||||
/**
|
||||
* Create a bare git repository to use as a local remote for testing sync
|
||||
*/
|
||||
When('I create a bare git repository at {string}', async function(this: ApplicationWorld, repoPath: string) {
|
||||
const actualPath = repoPath.replace('{tmpDir}', wikiTestRootPath);
|
||||
|
||||
// Remove if exists
|
||||
if (await fs.pathExists(actualPath)) {
|
||||
await fs.remove(actualPath);
|
||||
}
|
||||
|
||||
// Create bare repository
|
||||
await fs.ensureDir(actualPath);
|
||||
await gitExec(['init', '--bare'], actualPath);
|
||||
});
|
||||
|
||||
/**
|
||||
* Verify that a commit with specific message exists in remote repository
|
||||
*/
|
||||
Then('the remote repository {string} should contain commit with message {string}', async function(this: ApplicationWorld, remotePath: string, commitMessage: string) {
|
||||
const actualRemotePath = remotePath.replace('{tmpDir}', wikiTestRootPath);
|
||||
|
||||
// Clone the remote to a temporary location to inspect it
|
||||
const temporaryClonePath = path.join(wikiTestRootPath, `temp-clone-${Date.now()}`);
|
||||
|
||||
try {
|
||||
await gitExec(['clone', actualRemotePath, temporaryClonePath], wikiTestRootPath);
|
||||
|
||||
// Check all branches for the commit message
|
||||
const branchResult = await gitExec(['branch', '-a'], temporaryClonePath);
|
||||
if (branchResult.exitCode !== 0) {
|
||||
throw new Error(`Failed to list branches: ${branchResult.stderr}`);
|
||||
}
|
||||
|
||||
// Try to find commits in any branch
|
||||
let foundCommit = false;
|
||||
const branches = branchResult.stdout.split('\n').filter(b => b.trim());
|
||||
|
||||
for (const branch of branches) {
|
||||
const branchName = branch.trim().replace('* ', '').replace('remotes/origin/', '');
|
||||
if (!branchName) continue;
|
||||
|
||||
try {
|
||||
// Checkout the branch
|
||||
await gitExec(['checkout', branchName], temporaryClonePath);
|
||||
|
||||
// Get commit log
|
||||
const result = await gitExec(['log', '--oneline', '-10'], temporaryClonePath);
|
||||
if (result.exitCode === 0 && result.stdout.includes(commitMessage)) {
|
||||
foundCommit = true;
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Branch might not exist or be checkable, continue to next
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundCommit) {
|
||||
// Get all logs from all branches for error message
|
||||
const allLogsResult = await gitExec(['log', '--all', '--oneline', '-20'], temporaryClonePath);
|
||||
throw new Error(`Commit with message "${commitMessage}" not found in any branch. Available commits:\n${allLogsResult.stdout}\n\nBranches:\n${branchResult.stdout}`);
|
||||
}
|
||||
} finally {
|
||||
// Clean up temporary clone
|
||||
if (await fs.pathExists(temporaryClonePath)) {
|
||||
await fs.remove(temporaryClonePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Verify that a file exists in remote repository
|
||||
*/
|
||||
Then('the remote repository {string} should contain file {string}', async function(this: ApplicationWorld, remotePath: string, filePath: string) {
|
||||
const actualRemotePath = remotePath.replace('{tmpDir}', wikiTestRootPath);
|
||||
|
||||
// Clone the remote to a temporary location to inspect it
|
||||
const temporaryClonePath = path.join(wikiTestRootPath, `temp-clone-${Date.now()}`);
|
||||
|
||||
try {
|
||||
await gitExec(['clone', actualRemotePath, temporaryClonePath], wikiTestRootPath);
|
||||
|
||||
// Check all branches for the file
|
||||
const branchResult = await gitExec(['branch', '-a'], temporaryClonePath);
|
||||
if (branchResult.exitCode !== 0) {
|
||||
throw new Error(`Failed to list branches: ${branchResult.stderr}`);
|
||||
}
|
||||
|
||||
let foundFile = false;
|
||||
const branches = branchResult.stdout.split('\n').filter(b => b.trim());
|
||||
|
||||
for (const branch of branches) {
|
||||
const branchName = branch.trim().replace('* ', '').replace('remotes/origin/', '');
|
||||
if (!branchName) continue;
|
||||
|
||||
try {
|
||||
// Checkout the branch
|
||||
await gitExec(['checkout', branchName], temporaryClonePath);
|
||||
|
||||
const fileFullPath = path.join(temporaryClonePath, filePath);
|
||||
if (await fs.pathExists(fileFullPath)) {
|
||||
foundFile = true;
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Branch might not exist or be checkable, continue to next
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundFile) {
|
||||
throw new Error(`File "${filePath}" not found in any branch of remote repository`);
|
||||
}
|
||||
} finally {
|
||||
// Clean up temporary clone
|
||||
if (await fs.pathExists(temporaryClonePath)) {
|
||||
await fs.remove(temporaryClonePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { DataTable, Then, When } from '@cucumber/cucumber';
|
||||
import { wikiTestRootPath } from '../supports/paths';
|
||||
import type { ApplicationWorld } from './application';
|
||||
|
||||
When('I wait for {float} seconds', async function(seconds: number) {
|
||||
|
|
@ -243,15 +244,54 @@ When('I type {string} in {string} element with selector {string}', async functio
|
|||
throw new Error('No current window is available');
|
||||
}
|
||||
|
||||
// Replace {tmpDir} placeholder with actual test root path
|
||||
const actualText = text.replace('{tmpDir}', wikiTestRootPath);
|
||||
|
||||
try {
|
||||
await currentWindow.waitForSelector(selector, { timeout: 10000 });
|
||||
const element = currentWindow.locator(selector);
|
||||
await element.fill(text);
|
||||
await element.fill(actualText);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to type in ${elementComment} element with selector "${selector}": ${error as Error}`);
|
||||
}
|
||||
});
|
||||
|
||||
When('I type in {string} elements with selectors:', async function(this: ApplicationWorld, elementDescriptions: string, dataTable: DataTable) {
|
||||
const currentWindow = this.currentWindow;
|
||||
if (!currentWindow) {
|
||||
throw new Error('No current window is available');
|
||||
}
|
||||
|
||||
const descriptions = elementDescriptions.split(' and ').map(d => d.trim());
|
||||
const rows = dataTable.raw();
|
||||
const errors: string[] = [];
|
||||
|
||||
if (descriptions.length !== rows.length) {
|
||||
throw new Error(`Mismatch: ${descriptions.length} element descriptions but ${rows.length} text/selector pairs provided`);
|
||||
}
|
||||
|
||||
// Type in elements sequentially to maintain order
|
||||
for (let index = 0; index < rows.length; index++) {
|
||||
const [text, selector] = rows[index];
|
||||
const elementComment = descriptions[index];
|
||||
|
||||
// Replace {tmpDir} placeholder with actual test root path
|
||||
const actualText = text.replace('{tmpDir}', wikiTestRootPath);
|
||||
|
||||
try {
|
||||
await currentWindow.waitForSelector(selector, { timeout: 10000 });
|
||||
const element = currentWindow.locator(selector);
|
||||
await element.fill(actualText);
|
||||
} catch (error) {
|
||||
errors.push(`Failed to type in "${elementComment}" with selector "${selector}": ${error as Error}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Failed to type in some elements:\n${errors.join('\n')}`);
|
||||
}
|
||||
});
|
||||
|
||||
When('I clear text in {string} element with selector {string}', async function(this: ApplicationWorld, elementComment: string, selector: string) {
|
||||
const currentWindow = this.currentWindow;
|
||||
if (!currentWindow) {
|
||||
|
|
|
|||
55
features/sync.feature
Normal file
55
features/sync.feature
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
Feature: Git Sync
|
||||
As a user
|
||||
I want to sync my wiki to a remote repository
|
||||
So that I can backup and share my content
|
||||
|
||||
Background:
|
||||
Given I cleanup test wiki so it could create a new one on start
|
||||
And I launch the TidGi application
|
||||
And I wait for the page to load completely
|
||||
Then I should see a "default wiki workspace" element with selector "div[data-testid^='workspace-']:has-text('wiki')"
|
||||
Then the browser view should be loaded and visible
|
||||
And I wait for SSE and watch-fs to be ready
|
||||
And I wait for "git initialization" log marker "[test-id-git-init-complete]"
|
||||
|
||||
@git @sync
|
||||
Scenario: Sync to local remote repository via application menu (commit and push)
|
||||
# Setup a bare git repository as local remote
|
||||
When I create a bare git repository at "{tmpDir}/remote-repo-menu.git"
|
||||
# Configure sync via edit workspace window
|
||||
When I open edit workspace window for workspace with name "wiki"
|
||||
And I switch to "editWorkspace" window
|
||||
And I wait for the page to load completely
|
||||
When I click on "saveAndSyncOptions accordion and syncToCloud toggle" elements with selectors:
|
||||
| [data-testid='preference-section-saveAndSyncOptions'] |
|
||||
| [data-testid='synced-local-workspace-switch'] |
|
||||
And I wait for 1 seconds
|
||||
When I type in "git url input and github username input and github email input and github token input" elements with selectors:
|
||||
| {tmpDir}/remote-repo-menu.git | label:has-text('Git仓库线上网址') + * input, label:has-text('Git Repo URL') + * input, input[aria-label='Git仓库线上网址'], input[aria-label='Git Repo URL'] |
|
||||
| test-user | [data-testid='github-userName-input'] input |
|
||||
| test@tidgi.test | [data-testid='github-email-input'] input |
|
||||
| test-token | [data-testid='github-token-input'] input |
|
||||
When I click on a "save workspace button" element with selector "[data-testid='edit-workspace-save-button']"
|
||||
# Wait for workspace to be saved (workspace.update triggers a restart which takes time)
|
||||
And I wait for 5 seconds
|
||||
When I switch to "main" window
|
||||
# Create a new tiddler to trigger sync
|
||||
When I create file "{tmpDir}/wiki/tiddlers/SyncMenuTestTiddler.tid" with content:
|
||||
"""
|
||||
created: 20250226090000000
|
||||
modified: 20250226090000000
|
||||
title: SyncMenuTestTiddler
|
||||
tags: SyncTest
|
||||
|
||||
This is a test tiddler for sync via menu feature.
|
||||
"""
|
||||
Then I wait for tiddler "SyncMenuTestTiddler" to be added by watch-fs
|
||||
# Clear previous test markers to ensure we're testing fresh sync operation
|
||||
When I clear test-id markers from logs
|
||||
# Use application menu to sync (commit and push)
|
||||
When I click menu "知识库 > 立即同步云端"
|
||||
# Wait for git sync to complete (not just commit)
|
||||
Then I wait for "git sync completed" log marker "[test-id-git-sync-complete]"
|
||||
# Verify the commit was pushed to remote by cloning the remote and checking
|
||||
Then the remote repository "{tmpDir}/remote-repo-menu.git" should contain commit with message "使用太记桌面版备份"
|
||||
And the remote repository "{tmpDir}/remote-repo-menu.git" should contain file "tiddlers/SyncMenuTestTiddler.tid"
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "tidgi",
|
||||
"productName": "TidGi",
|
||||
"description": "Customizable personal knowledge-base with Github as unlimited storage and blogging platform.",
|
||||
"version": "0.13.0-prerelease10",
|
||||
"version": "0.13.0-prerelease11",
|
||||
"license": "MPL 2.0",
|
||||
"packageManager": "pnpm@10.18.2",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -326,17 +326,35 @@ export class Git implements IGitService {
|
|||
return;
|
||||
}
|
||||
|
||||
observable.subscribe(this.getWorkerMessageObserver(wikiFolderPath, () => {}, reject, workspaceID));
|
||||
let hasChanges = false;
|
||||
observable.subscribe({
|
||||
next: (messageObject: IGitLogMessage) => {
|
||||
// Log the message
|
||||
if (messageObject.level === 'error') {
|
||||
const errorMessage = (messageObject.error).message;
|
||||
// if workspace exists, show notification in workspace, else use dialog instead
|
||||
if (workspaceID === undefined) {
|
||||
this.createFailedDialog(errorMessage, wikiFolderPath);
|
||||
} else {
|
||||
this.createFailedNotification(errorMessage, workspaceID);
|
||||
}
|
||||
// Reject the promise on error to prevent service restart
|
||||
reject(messageObject.error);
|
||||
return;
|
||||
}
|
||||
const { meta } = messageObject;
|
||||
if (typeof meta === 'object' && meta !== null && 'step' in meta && stepsAboutChange.includes((meta as { step: GitStep }).step)) {
|
||||
hasChanges = true;
|
||||
const { message, meta, level } = messageObject;
|
||||
if (typeof meta === 'object' && meta !== null && 'step' in meta) {
|
||||
this.popGitErrorNotificationToUser((meta as { step: GitStep }).step, message);
|
||||
// Check if this step indicates changes
|
||||
if (stepsAboutChange.includes((meta as { step: GitStep }).step)) {
|
||||
hasChanges = true;
|
||||
}
|
||||
}
|
||||
logger.log(level, translateMessage(message), meta);
|
||||
},
|
||||
error: (error) => {
|
||||
// this normally won't happen. And will become unhandled error. Because Observable error can't be catch, don't know why.
|
||||
reject(error as Error);
|
||||
},
|
||||
complete: () => {
|
||||
resolve(hasChanges);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* Utility functions for creating Git-related menu items
|
||||
* This file is safe to import from both frontend and backend code
|
||||
*/
|
||||
import type { IGitService } from '@services/git/interface';
|
||||
import type { IGitService, IGitUserInfos } from '@services/git/interface';
|
||||
import { DeferredMenuItemConstructorOptions } from '@services/menu/interface';
|
||||
import { IWindowService } from '@services/windows/interface';
|
||||
import { WindowNames } from '@services/windows/WindowProperties';
|
||||
|
|
@ -118,6 +118,7 @@ export function createBackupMenuItems(
|
|||
* @param gitService Git service instance (or Pick with commitAndSync)
|
||||
* @param aiEnabled Whether AI-generated commit messages are enabled
|
||||
* @param isOnline Whether the network is online (optional, defaults to true)
|
||||
* @param userInfo User authentication info for git operations
|
||||
* @returns Array of menu items
|
||||
*/
|
||||
export function createSyncMenuItems(
|
||||
|
|
@ -125,7 +126,8 @@ export function createSyncMenuItems(
|
|||
t: TFunction,
|
||||
gitService: Pick<IGitService, 'commitAndSync'>,
|
||||
aiEnabled: boolean,
|
||||
isOnline?: boolean,
|
||||
isOnline: boolean,
|
||||
userInfo: IGitUserInfos | undefined,
|
||||
): DeferredMenuItemConstructorOptions[];
|
||||
|
||||
/**
|
||||
|
|
@ -135,6 +137,7 @@ export function createSyncMenuItems(
|
|||
* @param gitService Git service instance (or Pick with commitAndSync)
|
||||
* @param aiEnabled Whether AI-generated commit messages are enabled
|
||||
* @param isOnline Whether the network is online (optional, defaults to true)
|
||||
* @param userInfo User authentication info for git operations
|
||||
* @param useDeferred Set to false for context menu
|
||||
* @returns Array of menu items
|
||||
*/
|
||||
|
|
@ -144,6 +147,7 @@ export function createSyncMenuItems(
|
|||
gitService: Pick<IGitService, 'commitAndSync'>,
|
||||
aiEnabled: boolean,
|
||||
isOnline: boolean,
|
||||
userInfo: IGitUserInfos | undefined,
|
||||
useDeferred: false,
|
||||
): import('electron').MenuItemConstructorOptions[];
|
||||
|
||||
|
|
@ -152,14 +156,15 @@ export function createSyncMenuItems(
|
|||
t: TFunction,
|
||||
gitService: Pick<IGitService, 'commitAndSync'>,
|
||||
aiEnabled: boolean,
|
||||
isOnline: boolean = true,
|
||||
isOnline: boolean,
|
||||
userInfo: IGitUserInfos | undefined,
|
||||
_useDeferred: boolean = true,
|
||||
): DeferredMenuItemConstructorOptions[] | import('electron').MenuItemConstructorOptions[] {
|
||||
if (!isWikiWorkspace(workspace) || !workspace.gitUrl) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { wikiFolderLocation } = workspace;
|
||||
const { wikiFolderLocation, gitUrl } = workspace;
|
||||
const offlineText = isOnline ? '' : ` (${t('ContextMenu.NoNetworkConnection')})`;
|
||||
|
||||
if (aiEnabled) {
|
||||
|
|
@ -172,6 +177,8 @@ export function createSyncMenuItems(
|
|||
dir: wikiFolderLocation,
|
||||
commitOnly: false,
|
||||
commitMessage: t('LOG.CommitBackupMessage'),
|
||||
remoteUrl: gitUrl,
|
||||
userInfo,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
@ -183,6 +190,8 @@ export function createSyncMenuItems(
|
|||
dir: wikiFolderLocation,
|
||||
commitOnly: false,
|
||||
// Don't provide commitMessage to trigger AI generation
|
||||
remoteUrl: gitUrl,
|
||||
userInfo,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
@ -197,6 +206,8 @@ export function createSyncMenuItems(
|
|||
await gitService.commitAndSync(workspace, {
|
||||
dir: wikiFolderLocation,
|
||||
commitOnly: false,
|
||||
remoteUrl: gitUrl,
|
||||
userInfo,
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import type { IAuthenticationService } from '@services/auth/interface';
|
||||
import { container } from '@services/container';
|
||||
import type { IContextService } from '@services/context/interface';
|
||||
import type { IGitService } from '@services/git/interface';
|
||||
import { i18n } from '@services/libs/i18n';
|
||||
import type { IMenuService } from '@services/menu/interface';
|
||||
import { DeferredMenuItemConstructorOptions } from '@services/menu/interface';
|
||||
import serviceIdentifier from '@services/serviceIdentifier';
|
||||
import { SupportedStorageServices } from '@services/types';
|
||||
import type { IWindowService } from '@services/windows/interface';
|
||||
import { WindowNames } from '@services/windows/WindowProperties';
|
||||
import type { IWorkspaceService } from '@services/workspaces/interface';
|
||||
|
|
@ -14,12 +17,23 @@ export async function registerMenu(): Promise<void> {
|
|||
const windowService = container.get<IWindowService>(serviceIdentifier.Window);
|
||||
const workspaceService = container.get<IWorkspaceService>(serviceIdentifier.Workspace);
|
||||
const gitService = container.get<IGitService>(serviceIdentifier.Git);
|
||||
const authService = container.get<IAuthenticationService>(serviceIdentifier.Authentication);
|
||||
const contextService = container.get<IContextService>(serviceIdentifier.Context);
|
||||
|
||||
const hasActiveWikiWorkspace = async (): Promise<boolean> => {
|
||||
const activeWorkspace = await workspaceService.getActiveWorkspace();
|
||||
return activeWorkspace !== undefined && isWikiWorkspace(activeWorkspace);
|
||||
};
|
||||
|
||||
const hasActiveSyncableWorkspace = async (): Promise<boolean> => {
|
||||
const activeWorkspace = await workspaceService.getActiveWorkspace();
|
||||
if (!activeWorkspace || !isWikiWorkspace(activeWorkspace)) return false;
|
||||
if (activeWorkspace.storageService === SupportedStorageServices.local) return false;
|
||||
if (!activeWorkspace.gitUrl) return false;
|
||||
const userInfo = await authService.getStorageServiceUserInfo(activeWorkspace.storageService);
|
||||
return userInfo !== undefined;
|
||||
};
|
||||
|
||||
// Build commit and sync menu items with dynamic enabled/click that checks activeWorkspace at runtime
|
||||
const commitMenuItems: DeferredMenuItemConstructorOptions[] = [
|
||||
{
|
||||
|
|
@ -67,8 +81,84 @@ export async function registerMenu(): Promise<void> {
|
|||
});
|
||||
}
|
||||
|
||||
// Build sync menu items
|
||||
const syncMenuItems: DeferredMenuItemConstructorOptions[] = [];
|
||||
|
||||
const isOnline = await contextService.isOnline();
|
||||
const offlineText = isOnline ? '' : ` (${i18n.t('ContextMenu.NoNetworkConnection')})`;
|
||||
|
||||
if (aiGenerateBackupTitleEnabled) {
|
||||
syncMenuItems.push(
|
||||
{
|
||||
label: () => i18n.t('ContextMenu.SyncNow') + offlineText,
|
||||
id: 'sync-now',
|
||||
visible: hasActiveSyncableWorkspace,
|
||||
enabled: async () => {
|
||||
const online = await contextService.isOnline();
|
||||
return online && await hasActiveSyncableWorkspace();
|
||||
},
|
||||
click: async () => {
|
||||
const activeWorkspace = await workspaceService.getActiveWorkspace();
|
||||
if (activeWorkspace !== undefined && isWikiWorkspace(activeWorkspace)) {
|
||||
const userInfo = await authService.getStorageServiceUserInfo(activeWorkspace.storageService);
|
||||
await gitService.commitAndSync(activeWorkspace, {
|
||||
dir: activeWorkspace.wikiFolderLocation,
|
||||
commitOnly: false,
|
||||
commitMessage: i18n.t('LOG.CommitBackupMessage'),
|
||||
remoteUrl: activeWorkspace.gitUrl,
|
||||
userInfo,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: () => i18n.t('ContextMenu.SyncNow') + i18n.t('ContextMenu.WithAI') + offlineText,
|
||||
id: 'sync-now-ai',
|
||||
visible: hasActiveSyncableWorkspace,
|
||||
enabled: async () => {
|
||||
const online = await contextService.isOnline();
|
||||
return online && await hasActiveSyncableWorkspace();
|
||||
},
|
||||
click: async () => {
|
||||
const activeWorkspace = await workspaceService.getActiveWorkspace();
|
||||
if (activeWorkspace !== undefined && isWikiWorkspace(activeWorkspace)) {
|
||||
const userInfo = await authService.getStorageServiceUserInfo(activeWorkspace.storageService);
|
||||
await gitService.commitAndSync(activeWorkspace, {
|
||||
dir: activeWorkspace.wikiFolderLocation,
|
||||
commitOnly: false,
|
||||
// Don't provide commitMessage to trigger AI generation
|
||||
remoteUrl: activeWorkspace.gitUrl,
|
||||
userInfo,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
} else {
|
||||
syncMenuItems.push({
|
||||
label: () => i18n.t('ContextMenu.SyncNow') + offlineText,
|
||||
id: 'sync-now',
|
||||
visible: hasActiveSyncableWorkspace,
|
||||
enabled: async () => {
|
||||
const online = await contextService.isOnline();
|
||||
return online && await hasActiveSyncableWorkspace();
|
||||
},
|
||||
click: async () => {
|
||||
const activeWorkspace = await workspaceService.getActiveWorkspace();
|
||||
if (activeWorkspace !== undefined && isWikiWorkspace(activeWorkspace)) {
|
||||
const userInfo = await authService.getStorageServiceUserInfo(activeWorkspace.storageService);
|
||||
await gitService.commitAndSync(activeWorkspace, {
|
||||
dir: activeWorkspace.wikiFolderLocation,
|
||||
commitOnly: false,
|
||||
commitMessage: i18n.t('LOG.CommitBackupMessage'),
|
||||
remoteUrl: activeWorkspace.gitUrl,
|
||||
userInfo,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Add to Wiki menu - basic items (each item checks for active wiki workspace)
|
||||
await menuService.insertMenu(
|
||||
'Wiki',
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ export async function getWorkspaceMenuTemplate(
|
|||
if (userInfo !== undefined) {
|
||||
const isOnline = await service.context.isOnline();
|
||||
|
||||
const syncItems = createSyncMenuItems(workspace, t, service.git, aiGenerateBackupTitleEnabled, isOnline, false);
|
||||
const syncItems = createSyncMenuItems(workspace, t, service.git, aiGenerateBackupTitleEnabled, isOnline, userInfo, false);
|
||||
template.push(...syncItems);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export function useForm(
|
|||
return;
|
||||
}
|
||||
await window.service.workspace.update(workspace.id, workspace);
|
||||
await window.service.native.log('info', '[test-id-WORKSPACE_SAVED]', { workspaceId: workspace.id, workspaceName: workspace.name });
|
||||
if (requestRestartAfterSave) {
|
||||
requestRestartCountDown();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue