Fix/sub wiki tag tree (#667)

* fix: different out path on macos

* fix: let go hibernation promise, so it's faster on macos

* log marker [test-id-TIDGI_MINI_WINDOW_CREATED]

* Skip registerShortcutByKey  in test

* fix: mini window on mac

* Remove useless log marker

* Update handleAttachToTidgiMiniWindow.ts

* fix: log marker check all log files

* lint

* fix: open in new window now showing wiki title

* Update package.json

* feat: basic load and save to sub wiki using in-tag-tree-of

* fix: load sub-wiki content and prevent echo

* fix: test and ui logic

* test: refactor subwiki test logic to a file

* refactor: shorten steps by using dedicated step, and test underlying micro steps

* fix: review

* refactor: remove outdated method signature

* test: unit cover adaptor subwiki routing

* Update FileSystemAdaptor.routing.test.ts

* fix: merge issue
This commit is contained in:
lin onetwo 2025-12-07 03:31:34 +08:00 committed by GitHub
parent 8a84d9b468
commit c2be8e4186
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
57 changed files with 2176 additions and 414 deletions

View file

@ -1,10 +1,10 @@
import FolderIcon from '@mui/icons-material/Folder';
import { AutocompleteRenderInputParams, MenuItem, Typography } from '@mui/material';
import { Autocomplete, AutocompleteRenderInputParams, MenuItem, Typography } from '@mui/material';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { isWikiWorkspace } from '@services/workspaces/interface';
import { CreateContainer, LocationPickerButton, LocationPickerContainer, LocationPickerInput, SoftLinkToMainWikiSelect, SubWikiTagAutoComplete } from './FormComponents';
import { CreateContainer, LocationPickerButton, LocationPickerContainer, LocationPickerInput, SoftLinkToMainWikiSelect } from './FormComponents';
import { useAvailableTags } from './useAvailableTags';
import { useValidateCloneWiki } from './useCloneWiki';
@ -63,7 +63,7 @@ export function CloneWikiForm({ form, isCreateMainWorkspace, errorInWhichCompone
label={t('AddWorkspace.MainWorkspaceLocation')}
helperText={form.mainWikiToLink.wikiFolderLocation &&
`${t('AddWorkspace.SubWorkspaceWillLinkTo')}
${form.mainWikiToLink.wikiFolderLocation}/tiddlers/${form.wikiFolderName}`}
${form.mainWikiToLink.wikiFolderLocation}`}
value={form.mainWikiToLinkIndex}
onChange={(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const index = Number(event.target.value);
@ -83,17 +83,23 @@ export function CloneWikiForm({ form, isCreateMainWorkspace, errorInWhichCompone
</MenuItem>
))}
</SoftLinkToMainWikiSelect>
<SubWikiTagAutoComplete
<Autocomplete<string, true, false, true>
multiple
freeSolo
options={availableTags}
value={form.tagName}
onInputChange={(_event: React.SyntheticEvent, value: string) => {
form.tagNameSetter(value);
value={form.tagNames}
onChange={(_event, newValue) => {
form.tagNamesSetter(newValue);
}}
slotProps={{
chip: {
variant: 'outlined',
},
}}
renderInput={(parameters: AutocompleteRenderInputParams) => (
<LocationPickerInput
{...parameters}
error={errorInWhichComponent.tagName}
error={errorInWhichComponent.tagNames}
label={t('AddWorkspace.TagName')}
helperText={t('AddWorkspace.TagNameHelp')}
/>

View file

@ -1,10 +1,10 @@
import FolderIcon from '@mui/icons-material/Folder';
import { AutocompleteRenderInputParams, MenuItem, Typography } from '@mui/material';
import { Autocomplete, AutocompleteRenderInputParams, MenuItem, Typography } from '@mui/material';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { isWikiWorkspace } from '@services/workspaces/interface';
import { CreateContainer, LocationPickerButton, LocationPickerContainer, LocationPickerInput, SoftLinkToMainWikiSelect, SubWikiTagAutoComplete } from './FormComponents';
import { CreateContainer, LocationPickerButton, LocationPickerContainer, LocationPickerInput, SoftLinkToMainWikiSelect } from './FormComponents';
import { useAvailableTags } from './useAvailableTags';
import { useValidateExistedWiki } from './useExistedWiki';
@ -32,8 +32,8 @@ export function ExistedWikiForm({
mainWikiToLinkIndex,
mainWikiToLinkSetter,
mainWorkspaceList,
tagName,
tagNameSetter,
tagNames,
tagNamesSetter,
} = form;
// Local state for the full path input - like NewWikiForm's direct state binding
@ -112,7 +112,7 @@ export function ExistedWikiForm({
label={t('AddWorkspace.MainWorkspaceLocation')}
helperText={mainWikiToLink.wikiFolderLocation &&
`${t('AddWorkspace.SubWorkspaceWillLinkTo')}
${mainWikiToLink.wikiFolderLocation}/tiddlers/${wikiFolderName}`}
${mainWikiToLink.wikiFolderLocation}`}
value={mainWikiToLinkIndex}
onChange={(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const index = Number(event.target.value);
@ -132,17 +132,23 @@ export function ExistedWikiForm({
</MenuItem>
))}
</SoftLinkToMainWikiSelect>
<SubWikiTagAutoComplete
<Autocomplete<string, true, false, true>
multiple
freeSolo
options={availableTags}
value={tagName}
onInputChange={(_event: React.SyntheticEvent, value: string) => {
tagNameSetter(value);
value={tagNames}
onChange={(_event, newValue) => {
tagNamesSetter(newValue);
}}
slotProps={{
chip: {
variant: 'outlined',
},
}}
renderInput={(parameters: AutocompleteRenderInputParams) => (
<LocationPickerInput
{...parameters}
error={errorInWhichComponent.tagName}
error={errorInWhichComponent.tagNames}
label={t('AddWorkspace.TagName')}
helperText={t('AddWorkspace.TagNameHelp')}
/>

View file

@ -1,9 +1,9 @@
import FolderIcon from '@mui/icons-material/Folder';
import { AutocompleteRenderInputParams, MenuItem, Typography } from '@mui/material';
import { Autocomplete, AutocompleteRenderInputParams, MenuItem, Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { isWikiWorkspace } from '@services/workspaces/interface';
import { CreateContainer, LocationPickerButton, LocationPickerContainer, LocationPickerInput, SoftLinkToMainWikiSelect, SubWikiTagAutoComplete } from './FormComponents';
import { CreateContainer, LocationPickerButton, LocationPickerContainer, LocationPickerInput, SoftLinkToMainWikiSelect } from './FormComponents';
import { useAvailableTags } from './useAvailableTags';
import type { IWikiWorkspaceFormProps } from './useForm';
@ -68,7 +68,7 @@ export function NewWikiForm({
label={t('AddWorkspace.MainWorkspaceLocation')}
helperText={form.mainWikiToLink.wikiFolderLocation &&
`${t('AddWorkspace.SubWorkspaceWillLinkTo')}
${form.mainWikiToLink.wikiFolderLocation}/tiddlers/subwiki/${form.wikiFolderName}`}
${form.mainWikiToLink.wikiFolderLocation}`}
value={form.mainWikiToLinkIndex}
slotProps={{ htmlInput: { 'data-testid': 'main-wiki-select' } }}
onChange={(event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
@ -89,16 +89,22 @@ export function NewWikiForm({
</MenuItem>
))}
</SoftLinkToMainWikiSelect>
<SubWikiTagAutoComplete
<Autocomplete<string, true, false, true>
multiple
freeSolo
options={availableTags}
value={form.tagName}
onInputChange={(_event: React.SyntheticEvent, value: string) => {
form.tagNameSetter(value);
value={form.tagNames}
onChange={(_event, newValue) => {
form.tagNamesSetter(newValue);
}}
slotProps={{
chip: {
variant: 'outlined',
},
}}
renderInput={(parameters: AutocompleteRenderInputParams) => (
<LocationPickerInput
error={errorInWhichComponent.tagName}
error={errorInWhichComponent.tagNames}
{...parameters}
label={t('AddWorkspace.TagName')}
helperText={t('AddWorkspace.TagNameHelp')}

View file

@ -47,8 +47,8 @@ const createMockForm = (overrides: Partial<IWikiWorkspaceForm> = {}): IWikiWorks
metadata: {},
} as unknown as IWorkspace,
],
tagName: '',
tagNameSetter: vi.fn(),
tagNames: [] as string[],
tagNamesSetter: vi.fn(),
gitRepoUrl: '',
gitRepoUrlSetter: vi.fn(),
gitUserInfo: undefined as IGitUserInfos | undefined,
@ -194,7 +194,7 @@ describe('NewWikiForm Component', () => {
const user = userEvent.setup();
const mockSetter = vi.fn();
const form = createMockForm({
tagNameSetter: mockSetter,
tagNamesSetter: mockSetter,
});
await renderNewWikiForm({
@ -206,7 +206,7 @@ describe('NewWikiForm Component', () => {
const tagInput = screen.getByTestId('tagname-autocomplete-input');
await user.type(tagInput, 'MyTag');
await user.keyboard('{enter}');
expect(mockSetter).toHaveBeenCalledWith('MyTag');
expect(mockSetter).toHaveBeenCalledWith(['MyTag']);
});
});
@ -231,7 +231,7 @@ describe('NewWikiForm Component', () => {
isCreateMainWorkspace: false,
errorInWhichComponent: {
mainWikiToLink: true,
tagName: true,
tagNames: true,
},
});
@ -276,11 +276,11 @@ describe('NewWikiForm Component', () => {
isCreateMainWorkspace: false,
});
// Because the text is rendered with a template literal and newlines, we need to use a regex
// The helper text shows the main wiki location that will be linked
expect(screen.getByText((content, _element) => {
// The actual text might have whitespace and newlines
const normalized = content.replace(/\s+/g, ' ').trim();
return normalized === 'AddWorkspace.SubWorkspaceWillLinkTo /main/wiki/tiddlers/subwiki/sub-wiki';
return normalized === 'AddWorkspace.SubWorkspaceWillLinkTo /main/wiki';
})).toBeInTheDocument();
});
});

View file

@ -48,7 +48,7 @@ export function useValidateCloneWiki(
form.gitRepoUrl,
form.gitUserInfo,
form.mainWikiToLink.wikiFolderLocation,
form.tagName,
form.tagNames,
errorInWhichComponentSetter,
]);
return [hasError, wikiCreationMessage, wikiCreationMessageSetter, hasErrorSetter];
@ -73,10 +73,8 @@ export function useCloneWiki(
await window.service.wiki.cloneSubWiki(
form.parentFolderLocation,
form.wikiFolderName,
form.mainWikiToLink.wikiFolderLocation,
form.gitRepoUrl,
form.gitUserInfo!,
form.tagName,
);
}
await callWikiInitialization(newWorkspaceConfig, wikiCreationMessageSetter, t, form.gitUserInfo, { from: WikiCreationMethod.Clone });

View file

@ -46,7 +46,7 @@ export function useValidateExistedWiki(
form.gitRepoUrl,
form.gitUserInfo,
form.mainWikiToLink.wikiFolderLocation,
form.tagName,
form.tagNames,
errorInWhichComponentSetter,
]);
return [hasError, wikiCreationMessage, wikiCreationMessageSetter, hasErrorSetter];
@ -81,14 +81,7 @@ export function useExistedWiki(
);
}
await window.service.wiki.ensureWikiExist(form.wikiFolderLocation, false);
await window.service.wiki.createSubWiki(
parentFolderLocationForExistedFolder,
wikiFolderNameForExistedFolder,
'subwiki',
form.mainWikiToLink.wikiFolderLocation,
form.tagName,
true,
);
await window.service.wiki.createSubWiki(parentFolderLocationForExistedFolder, wikiFolderNameForExistedFolder, true);
}
await callWikiInitialization(newWorkspaceConfig, wikiCreationMessageSetter, t, form.gitUserInfo, { from: WikiCreationMethod.LoadExisting });
} catch (error) {

View file

@ -50,7 +50,7 @@ export function useWikiWorkspaceForm(options?: { fromExisted: boolean }) {
return firstMainWiki ? { wikiFolderLocation: firstMainWiki.wikiFolderLocation, port: firstMainWiki.port, id: firstMainWiki.id } : { wikiFolderLocation: '', port: 0, id: '' };
},
);
const [tagName, tagNameSetter] = useState<string>('');
const [tagNames, tagNamesSetter] = useState<string[]>([]);
let mainWikiToLinkIndex = mainWorkspaceList.findIndex((workspace) => workspace.id === mainWikiToLink.id);
if (mainWikiToLinkIndex < 0) {
mainWikiToLinkIndex = 0;
@ -123,8 +123,8 @@ export function useWikiWorkspaceForm(options?: { fromExisted: boolean }) {
wikiPortSetter,
mainWikiToLink,
mainWikiToLinkSetter,
tagName,
tagNameSetter,
tagNames,
tagNamesSetter,
gitRepoUrl,
gitRepoUrlSetter,
parentFolderLocation,
@ -162,7 +162,7 @@ export function workspaceConfigFromForm(form: INewWikiRequiredFormData, isCreate
mainWikiID: isCreateMainWorkspace ? null : form.mainWikiToLink.id,
name: form.wikiFolderName,
storageService: form.storageProvider,
tagName: isCreateMainWorkspace ? null : form.tagName,
tagNames: isCreateMainWorkspace ? [] : form.tagNames,
port: form.wikiPort,
wikiFolderLocation: form.wikiFolderLocation!,
backupOnInterval: true,
@ -173,6 +173,9 @@ export function workspaceConfigFromForm(form: INewWikiRequiredFormData, isCreate
excludedPlugins: [],
enableHTTPAPI: false,
enableFileSystemWatch: true,
includeTagTree: false,
fileSystemPathFilterEnable: false,
fileSystemPathFilter: null,
lastNodeJSArgv: [],
};
}

View file

@ -51,7 +51,7 @@ export function useValidateNewWiki(
form.gitRepoUrl,
form.gitUserInfo,
form.mainWikiToLink.wikiFolderLocation,
form.tagName,
form.tagNames,
errorInWhichComponentSetter,
]);
@ -80,7 +80,7 @@ export function useNewWiki(
await window.service.wiki.copyWikiTemplate(form.parentFolderLocation, form.wikiFolderName);
}
} else {
await window.service.wiki.createSubWiki(form.parentFolderLocation, form.wikiFolderName, 'subwiki', form.mainWikiToLink.wikiFolderLocation, form.tagName);
await window.service.wiki.createSubWiki(form.parentFolderLocation, form.wikiFolderName);
}
await callWikiInitialization(newWorkspaceConfig, wikiCreationMessageSetter, t, form.gitUserInfo, { notClose: options?.notClose, from: WikiCreationMethod.Create });
} catch (error) {