diff --git a/localization/locales/en/translation.json b/localization/locales/en/translation.json index f49d321c..16276665 100644 --- a/localization/locales/en/translation.json +++ b/localization/locales/en/translation.json @@ -19,6 +19,8 @@ "CustomServerUrlDescription": "Base URL of the OAuth server (e.g., http://127.0.0.1:8888)", "ExistedWikiLocation": "Existed Wiki Location", "ExtractedWikiFolderName": "Converted WIKI folder name", + "FilterExpression": "filter expression", + "FilterExpressionHelp": "One TiddlyWiki filter expression per line; any match will be saved to this workspace. For example: [in-tagtree-of[Calendar]!tag[Public]]", "GitBranch": "Git Branch", "GitBranchDescription": "Git branch to use (default: main)", "GitDefaultBranchDescription": "The default branch of your Git, Github changed it from master to main after that event", @@ -70,6 +72,8 @@ "TagNameHelp": "Tiddlers with this Tag will be add to this sub-wiki (you can add or change this Tag later, by right-click workspace Icon and choose Edit Workspace)", "TagNameHelpForMain": "New entries with this tag will be prioritized for storage in this workspace.", "ThisPathIsNotAWikiFolder": "The directory is not a Wiki folder \"{{wikiPath}}\"", + "UseFilter": "Use filters", + "UseFilterHelp": "Use filter expressions instead of tags to match entries and determine whether to save them in the current workspace.", "WaitForLogin": "Wait for Login", "WikiExisted": "Wiki already exists at this location \"{{newWikiPath}}\"", "WikiNotStarted": "Wiki is not started or not loaded", diff --git a/localization/locales/fr/translation.json b/localization/locales/fr/translation.json index 9ff69598..af477da4 100644 --- a/localization/locales/fr/translation.json +++ b/localization/locales/fr/translation.json @@ -19,6 +19,8 @@ "CustomServerUrlDescription": "URL de base du serveur OAuth (par exemple : http://127.0.0.1:8888)", "ExistedWikiLocation": "Emplacement du Wiki existant", "ExtractedWikiFolderName": "Nom du dossier WIKI converti", + "FilterExpression": "expression du filtre", + "FilterExpressionHelp": "Un filtre d'expression TiddlyWiki par ligne, toute correspondance sera enregistrée dans cet espace de travail. Par exemple : [in-tagtree-of[Calendar]!tag[Public]]", "GitBranch": "Branche Git", "GitBranchDescription": "Branche Git à utiliser (par défaut : main)", "GitDefaultBranchDescription": "La branche par défaut de votre Git, Github l'a changée de master à main après cet événement", @@ -70,6 +72,8 @@ "TagNameHelp": "Les tiddlers avec cette étiquette seront ajoutés à ce sous-wiki (vous pouvez ajouter ou modifier cette étiquette plus tard, en cliquant avec le bouton droit sur l'icône de l'espace de travail et en choisissant Modifier l'espace de travail)", "TagNameHelpForMain": "Les nouvelles entrées avec cette étiquette seront prioritairement enregistrées dans cet espace de travail.", "ThisPathIsNotAWikiFolder": "Le répertoire n'est pas un dossier Wiki \"{{wikiPath}}\"", + "UseFilter": "utiliser un filtre", + "UseFilterHelp": "Utilisez des expressions de filtre plutôt que des étiquettes pour correspondre aux entrées et décider si elles doivent être stockées dans l'espace de travail actuel.", "WaitForLogin": "Attendre la connexion", "WikiExisted": "Le Wiki existe déjà à cet emplacement \"{{newWikiPath}}\"", "WikiNotStarted": "Le Wiki n'est pas démarré ou n'est pas chargé", diff --git a/localization/locales/ja/translation.json b/localization/locales/ja/translation.json index ad444b73..d8813658 100644 --- a/localization/locales/ja/translation.json +++ b/localization/locales/ja/translation.json @@ -19,6 +19,8 @@ "CustomServerUrlDescription": "OAuthサーバーのベースURL(例:http://127.0.0.1:8888)", "ExistedWikiLocation": "既存のWikiの場所", "ExtractedWikiFolderName": "変換されたWIKIフォルダ名", + "FilterExpression": "フィルター式", + "FilterExpressionHelp": "TiddlyWikiフィルター式を1行ずつ入力してください。いずれかが一致すると、このワークスペースに保存されます。例:[in-tagtree-of[Calendar]!tag[Public]]", "GitBranch": "Git ブランチ", "GitBranchDescription": "使用するGitブランチ(デフォルト:main)", "GitDefaultBranchDescription": "Gitのデフォルトブランチ。Githubはそのイベント後にmasterからmainに変更しました", @@ -70,6 +72,8 @@ "TagNameHelp": "このタグを持つTiddlerはこのサブWikiに追加されます(後で右クリックしてワークスペースアイコンを選択し、ワークスペースを編集することでこのタグを追加または変更できます)", "TagNameHelpForMain": "このタグが付いた新しいエントリは、このワークスペースに優先的に保存されます", "ThisPathIsNotAWikiFolder": "このディレクトリはWikiフォルダではありません \"{{wikiPath}}\"", + "UseFilter": "フィルターを使用する", + "UseFilterHelp": "フィルター式を使用してエントリをマッチングし、現在のワークスペースに保存するかどうかを決定します。タグではなくフィルター式を用います。", "WaitForLogin": "ログインを待っています", "WikiExisted": "この場所にWikiが既に存在します \"{{newWikiPath}}\"", "WikiNotStarted": "Wikiが開始されていないか、読み込まれていません", diff --git a/localization/locales/ru/translation.json b/localization/locales/ru/translation.json index cd2de7cc..dd2248a9 100644 --- a/localization/locales/ru/translation.json +++ b/localization/locales/ru/translation.json @@ -19,6 +19,8 @@ "CustomServerUrlDescription": "Базовый URL сервера OAuth (например: http://127.0.0.1:8888)", "ExistedWikiLocation": "Местоположение существующей Wiki", "ExtractedWikiFolderName": "Имя папки извлеченной WIKI", + "FilterExpression": "выражение фильтра", + "FilterExpressionHelp": "Каждая строка содержит выражение фильтра TiddlyWiki. Если хотя бы одно из них совпадает, запись сохраняется в этой рабочей области. Например: [in-tagtree-of[Calendar]!tag[Public]].", "GitBranch": "Ветка Git", "GitBranchDescription": "Используемая ветка Git (по умолчанию: main)", "GitDefaultBranchDescription": "Основная ветка вашего Git, Github изменил ее с master на main после того событ��я", @@ -70,6 +72,8 @@ "TagNameHelp": "Тидлеры с этим тегом будут добавлены в эту под-Wiki (вы можете добавить или изменить этот тег позже, щелкнув правой кнопкой мыши значок рабочего пространства и выбрав Настроить рабочее пространство)", "TagNameHelpForMain": "Новые записи с этой меткой будут сохраняться в первую очередь в этой рабочей области.", "ThisPathIsNotAWikiFolder": "Каталог не является папкой Wiki \"{{wikiPath}}\"", + "UseFilter": "использовать фильтр", + "UseFilterHelp": "Используйте выражения фильтров вместо меток для сопоставления записей и определения, следует ли сохранять их в текущей рабочей области.", "WaitForLogin": "Ожидание входа", "WikiExisted": "Wiki уже существует в этом месте \"{{newWikiPath}}\"", "WikiNotStarted": "Wiki не запущена или не загружена", diff --git a/localization/locales/zh-Hant/translation.json b/localization/locales/zh-Hant/translation.json index ecfbe9e4..855694e2 100644 --- a/localization/locales/zh-Hant/translation.json +++ b/localization/locales/zh-Hant/translation.json @@ -19,6 +19,8 @@ "CustomServerUrlDescription": "OAuth 伺服器的基礎 URL(例如:http://127.0.0.1:8888)", "ExistedWikiLocation": "現有的知識庫的位置", "ExtractedWikiFolderName": "轉換後的知識庫文件夾名稱", + "FilterExpression": "篩選器表達式", + "FilterExpressionHelp": "每行一個TiddlyWiki篩選器表達式,任一匹配即存入此工作區。例如 [in-tagtree-of[Calendar]!tag[Public]]", "GitBranch": "Git 分支", "GitBranchDescription": "要使用的 Git 分支(預設:main)", "GitDefaultBranchDescription": "你的Git的預設分支,Github在黑命貴事件後將其從master改為了main", @@ -70,6 +72,8 @@ "TagNameHelp": "加上此標籤的筆記將會自動被放入這個子知識庫內(可先不填,之後右鍵點擊這個工作區的圖示選擇編輯工作區修改)", "TagNameHelpForMain": "帶有此標籤的新條目將優先保存在此工作區", "ThisPathIsNotAWikiFolder": "該目錄不是一個知識庫文件夾 \"{{wikiPath}}\"", + "UseFilter": "使用篩選器", + "UseFilterHelp": "用篩選器運算式而不是標籤來匹配條目,決定是否存入當前工作區", "WaitForLogin": "等待登錄", "WikiExisted": "知識庫已經存在於該位置 \"{{newWikiPath}}\"", "WikiNotStarted": "知識庫 頁面未成功啟動或未成功載入", diff --git a/src/components/__tests__/KeyboardShortcutRegister.test.tsx b/src/components/__tests__/KeyboardShortcutRegister.test.tsx index 47d0706e..9227c131 100644 --- a/src/components/__tests__/KeyboardShortcutRegister.test.tsx +++ b/src/components/__tests__/KeyboardShortcutRegister.test.tsx @@ -13,6 +13,10 @@ const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( ); +// Helper to get the correct modifier key based on platform +// On macOS, ctrlKey is displayed as 'Cmd', on other platforms as 'Ctrl' +const getCtrlModifier = () => process.platform === 'darwin' ? 'Cmd' : 'Ctrl'; + describe('KeyboardShortcutRegister Component', () => { let mockOnChange: ReturnType; @@ -158,6 +162,7 @@ describe('KeyboardShortcutRegister Component', () => { const dialogContent = screen.getByTestId('shortcut-dialog-content'); // Simulate keyboard event with Ctrl+Shift+T + // On macOS, ctrlKey is displayed as 'Cmd' fireEvent.keyDown(dialogContent, { key: 'T', ctrlKey: true, @@ -167,7 +172,7 @@ describe('KeyboardShortcutRegister Component', () => { await waitFor(() => { const display = screen.getByTestId('shortcut-display'); - expect(display).toHaveTextContent('Ctrl+Shift+T'); + expect(display).toHaveTextContent(`${getCtrlModifier()}+Shift+T`); }); }); @@ -284,7 +289,7 @@ describe('KeyboardShortcutRegister Component', () => { await waitFor(() => { const display = screen.getByTestId('shortcut-display'); - expect(display).toHaveTextContent('Ctrl+A'); + expect(display).toHaveTextContent(`${getCtrlModifier()}+A`); }); // Press second combination - should replace @@ -297,7 +302,7 @@ describe('KeyboardShortcutRegister Component', () => { await waitFor(() => { const display = screen.getByTestId('shortcut-display'); - expect(display).toHaveTextContent('Ctrl+Shift+B'); + expect(display).toHaveTextContent(`${getCtrlModifier()}+Shift+B`); }); }); }); @@ -372,14 +377,14 @@ describe('KeyboardShortcutRegister Component', () => { await waitFor(() => { const display = screen.getByTestId('shortcut-display'); - expect(display).toHaveTextContent('Ctrl+N'); + expect(display).toHaveTextContent(`${getCtrlModifier()}+N`); }); // Press Enter to confirm fireEvent.keyDown(document, { key: 'Enter', code: 'Enter' }); await waitFor(() => { - expect(mockOnChange).toHaveBeenCalledWith('Ctrl+N'); + expect(mockOnChange).toHaveBeenCalledWith(`${getCtrlModifier()}+N`); }); }); @@ -406,7 +411,7 @@ describe('KeyboardShortcutRegister Component', () => { await waitFor(() => { const display = screen.getByTestId('shortcut-display'); - expect(display).toHaveTextContent('Ctrl+B'); + expect(display).toHaveTextContent(`${getCtrlModifier()}+B`); }); // Press ESC to cancel without saving @@ -485,6 +490,8 @@ describe('KeyboardShortcutRegister Component', () => { }); // Simulate Ctrl+X key press on document + // On macOS, ctrlKey is displayed as 'Cmd', on other platforms as 'Ctrl' + const expectedModifier = process.platform === 'darwin' ? 'Cmd' : 'Ctrl'; fireEvent.keyDown(document, { key: 'X', ctrlKey: true, @@ -493,14 +500,14 @@ describe('KeyboardShortcutRegister Component', () => { // Wait for the key combination to be processed await waitFor(() => { - expect(screen.getByText('Ctrl+X')).toBeInTheDocument(); + expect(screen.getByText(`${expectedModifier}+X`)).toBeInTheDocument(); }); // Press Enter to confirm fireEvent.keyDown(document, { key: 'Enter', code: 'Enter' }); await waitFor(() => { - expect(customOnChange).toHaveBeenCalledWith('Ctrl+X'); + expect(customOnChange).toHaveBeenCalledWith(`${expectedModifier}+X`); }); }); }); diff --git a/src/services/wiki/plugin/watchFileSystemAdaptor/FileSystemAdaptor.ts b/src/services/wiki/plugin/watchFileSystemAdaptor/FileSystemAdaptor.ts index de944de9..4cc017c7 100644 --- a/src/services/wiki/plugin/watchFileSystemAdaptor/FileSystemAdaptor.ts +++ b/src/services/wiki/plugin/watchFileSystemAdaptor/FileSystemAdaptor.ts @@ -20,10 +20,8 @@ export class FileSystemAdaptor { boot: typeof $tw.boot; logger: Logger; workspaceID: string; - /** All workspaces (main + sub-wikis) that have tagName configured, sorted by order */ - protected wikisWithTag: IWikiWorkspace[] = []; - /** Map of tagName -> workspace for O(1) tag lookup instead of O(n) find */ - protected tagNameToWiki: Map = new Map(); + /** All workspaces (main + sub-wikis) that have tagName or filter configured, sorted by order */ + protected wikisWithRouting: IWikiWorkspace[] = []; /** Cached extension filters from $:/config/FileSystemExtensions. Requires restart to reflect changes. */ protected extensionFilters: string[] | undefined; protected watchPathBase!: string; @@ -73,15 +71,13 @@ export class FileSystemAdaptor { protected async updateSubWikisCache(): Promise { try { if (!this.workspaceID) { - this.wikisWithTag = []; - this.tagNameToWiki.clear(); + this.wikisWithRouting = []; return; } const currentWorkspace = await workspace.get(this.workspaceID); if (!currentWorkspace) { - this.wikisWithTag = []; - this.tagNameToWiki.clear(); + this.wikisWithRouting = []; return; } @@ -112,17 +108,9 @@ export class FileSystemAdaptor { return isMain || isSubWiki; }; - const workspacesWithTag = allWorkspaces.filter(isWikiWorkspaceWithRouting).sort(workspaceSorter); + const workspacesWithRouting = allWorkspaces.filter(isWikiWorkspaceWithRouting).sort(workspaceSorter); - this.wikisWithTag = workspacesWithTag; - - this.tagNameToWiki.clear(); - for (const workspaceWithTag of workspacesWithTag) { - // Build map for all tag names in this workspace - for (const tagName of workspaceWithTag.tagNames) { - this.tagNameToWiki.set(tagName, workspaceWithTag); - } - } + this.wikisWithRouting = workspacesWithRouting; } catch (error) { this.logger.alert('filesystem: Failed to update sub-wikis cache:', error); } @@ -196,10 +184,10 @@ export class FileSystemAdaptor { // Check if existing file is already in the correct directory // If so, just return the existing fileInfo to avoid echo loops if (existingFileInfo?.filepath) { - const existingDir = path.dirname(existingFileInfo.filepath); + const existingDirectory = path.dirname(existingFileInfo.filepath); // For sub-wikis, check if file is in that wiki's folder (or subfolder) // For main wiki, check if file is in main wiki's tiddlers folder (or subfolder) - const normalizedExisting = path.normalize(existingDir); + const normalizedExisting = path.normalize(existingDirectory); const normalizedTarget = path.normalize(targetDirectory); // Check if existing file is within the target directory tree @@ -225,26 +213,13 @@ export class FileSystemAdaptor { * Match a tiddler to a workspace based on routing rules. * Checks workspaces in order (priority) and returns the first match. * - * For each workspace: - * 1. If fileSystemPathFilterEnable is enabled, use custom filter expressions (one per line, any match wins) - * 2. Else try direct tag match (including if tiddler's title IS one of the tagNames - it's a "tag tiddler") - * 3. Else if includeTagTree is enabled, use in-tagtree-of filter + * For each workspace, checks in order (any match wins): + * 1. Direct tag match (including if tiddler's title IS one of the tagNames - it's a "tag tiddler") + * 2. If includeTagTree is enabled, use in-tagtree-of filter for recursive tag matching + * 3. If fileSystemPathFilterEnable is enabled, use custom filter expressions (one per line, any match wins) */ protected matchTitleToWiki(title: string, tags: string[]): IWikiWorkspace | undefined { - for (const wiki of this.wikisWithTag) { - // If fileSystemPathFilterEnable is enabled, use the custom filter expressions - if (wiki.fileSystemPathFilterEnable && wiki.fileSystemPathFilter) { - // Split by newlines and try each filter - const filters = wiki.fileSystemPathFilter.split('\n').map(f => f.trim()).filter(f => f.length > 0); - for (const filter of filters) { - const result = $tw.wiki.filterTiddlers(filter, undefined, $tw.wiki.makeTiddlerIterator([title])); - if (result.length > 0) { - return wiki; - } - } - continue; - } - + for (const wiki of this.wikisWithRouting) { // Direct tag match - check if any of the tiddler's tags match any of the wiki's tagNames // Also check if the tiddler's title IS one of the tagNames (it's a "tag tiddler" that defines that tag) if (wiki.tagNames.length > 0) { @@ -264,6 +239,18 @@ export class FileSystemAdaptor { } } } + + // Custom filter match if enabled + if (wiki.fileSystemPathFilterEnable && wiki.fileSystemPathFilter) { + // Split by newlines and try each filter + const filters = wiki.fileSystemPathFilter.split('\n').map(f => f.trim()).filter(f => f.length > 0); + for (const filter of filters) { + const result = $tw.wiki.filterTiddlers(filter, undefined, $tw.wiki.makeTiddlerIterator([title])); + if (result.length > 0) { + return wiki; + } + } + } } return undefined; } diff --git a/src/services/wiki/plugin/watchFileSystemAdaptor/__tests__/FileSystemAdaptor.routing.test.ts b/src/services/wiki/plugin/watchFileSystemAdaptor/__tests__/FileSystemAdaptor.routing.test.ts index b8ebf2f3..0e094377 100644 --- a/src/services/wiki/plugin/watchFileSystemAdaptor/__tests__/FileSystemAdaptor.routing.test.ts +++ b/src/services/wiki/plugin/watchFileSystemAdaptor/__tests__/FileSystemAdaptor.routing.test.ts @@ -165,9 +165,31 @@ describe('FileSystemAdaptor - Routing Logic', () => { ); }); - it('should pass existing fileInfo with overwrite flag', async () => { + it('should return existing fileInfo with overwrite flag when file is in correct directory', async () => { const existingFileInfo: IFileInfo = { - filepath: '/test/old.tid', + filepath: '/test/wiki/tiddlers/old.tid', // Already in the correct tiddlers directory + type: 'application/x-tiddler', + hasMetaFile: false, + }; + + // @ts-expect-error - TiddlyWiki global + global.$tw.boot.files['TestTiddler'] = existingFileInfo; + + const tiddler: Tiddler = { + fields: { title: 'TestTiddler', tags: [] }, + } as unknown as Tiddler; + + const result = await adaptor.getTiddlerFileInfo(tiddler); + + // Should return the existing fileInfo with overwrite flag, not call generateTiddlerFileInfo + expect(result).toEqual({ ...existingFileInfo, overwrite: true }); + // Should NOT call generateTiddlerFileInfo since file is already in correct location + expect(mockUtils.generateTiddlerFileInfo).not.toHaveBeenCalled(); + }); + + it('should regenerate fileInfo when file is in wrong directory', async () => { + const existingFileInfo: IFileInfo = { + filepath: '/wrong/directory/old.tid', // In wrong directory type: 'application/x-tiddler', hasMetaFile: false, }; @@ -181,14 +203,8 @@ describe('FileSystemAdaptor - Routing Logic', () => { await adaptor.getTiddlerFileInfo(tiddler); - expect(mockUtils.generateTiddlerFileInfo).toHaveBeenCalledWith( - tiddler, - expect.objectContaining({ - fileInfo: expect.objectContaining({ - overwrite: true, - }), - }), - ); + // Should call generateTiddlerFileInfo since file needs to be moved + expect(mockUtils.generateTiddlerFileInfo).toHaveBeenCalled(); }); it('should throw error when wikiTiddlersPath is not set', async () => { @@ -439,8 +455,7 @@ describe('FileSystemAdaptor - Routing Logic', () => { // Manually trigger cache update and wait for it await adaptor['updateSubWikisCache'](); - expect(adaptor['wikisWithTag']).toEqual([]); - expect(adaptor['tagNameToWiki'].size).toBe(0); + expect(adaptor['wikisWithRouting']).toEqual([]); }); it('should clear cache when currentWorkspace is not found', async () => { @@ -455,8 +470,7 @@ describe('FileSystemAdaptor - Routing Logic', () => { // Manually trigger cache update and wait for it await adaptor['updateSubWikisCache'](); - expect(adaptor['wikisWithTag']).toEqual([]); - expect(adaptor['tagNameToWiki'].size).toBe(0); + expect(adaptor['wikisWithRouting']).toEqual([]); }); it('should handle errors in updateSubWikisCache gracefully', async () => { diff --git a/src/services/wiki/plugin/watchFileSystemAdaptor/readme.tid b/src/services/wiki/plugin/watchFileSystemAdaptor/readme.tid index 74c07143..7125f734 100644 --- a/src/services/wiki/plugin/watchFileSystemAdaptor/readme.tid +++ b/src/services/wiki/plugin/watchFileSystemAdaptor/readme.tid @@ -8,11 +8,11 @@ This plugin provides an enhanced filesystem adaptor that automatically routes ti !!! How It Works # Queries workspace information from TidGi's main process via IPC -# Checks each tiddler's tags/filters against sub-workspace routing rules: -#* Multiple tag names (`tagNames`) - matches if any tag matches +# Checks each tiddler against sub-workspace routing rules (any match wins): +#* Direct tag match - if any of the tiddler's tags match any of the workspace's `tagNames` #* Tag tiddlers - if a tiddler's title IS one of the `tagNames`, it's also routed (e.g., tiddler "Test" goes to sub-wiki with tagNames=["Test"]) -#* Tag tree (`includeTagTree`) - recursive tag hierarchy using `in-tagtree-of` -#* Custom filter expressions (`fileSystemPathFilter`) - one per line, any match wins +#* Tag tree (`includeTagTree`) - if enabled, uses `in-tagtree-of` filter for recursive tag hierarchy matching +#* Custom filter expressions (`fileSystemPathFilter`) - if enabled, uses custom TiddlyWiki filter expressions (one per line, any match wins) # Routes tiddlers with matching rules to the corresponding sub-wiki's root folder # Falls back to standard `$:/config/FileSystemPaths` logic for non-matching tiddlers # Only moves files when target directory changes (avoids echo loops) @@ -30,6 +30,7 @@ This plugin provides an enhanced filesystem adaptor that automatically routes ti * Supports multiple tags per workspace * Supports tag hierarchy matching * Supports custom TiddlyWiki filter expressions +* All routing methods work together (tag match, tag tree, custom filter) * Handles tag changes - moves tiddlers when tags are modified * Tag tiddlers (tiddlers whose title matches a tag name) are also routed correctly * More robust than string manipulation diff --git a/src/services/wiki/wikiWorker/loadWikiTiddlersWithSubWikis.ts b/src/services/wiki/wikiWorker/loadWikiTiddlersWithSubWikis.ts index 89b3ef2d..d19c1cc5 100644 --- a/src/services/wiki/wikiWorker/loadWikiTiddlersWithSubWikis.ts +++ b/src/services/wiki/wikiWorker/loadWikiTiddlersWithSubWikis.ts @@ -1,5 +1,4 @@ import type { IWikiWorkspace } from '@services/workspaces/interface'; -import path from 'path'; import type { TiddlyWiki } from 'tiddlywiki'; /** diff --git a/src/services/workspaces/interface.ts b/src/services/workspaces/interface.ts index c0889639..de25e4e4 100644 --- a/src/services/workspaces/interface.ts +++ b/src/services/workspaces/interface.ts @@ -142,7 +142,7 @@ export interface IWikiWorkspace extends IDedicatedWorkspace { /** * When enabled, tiddlers that are indirectly tagged (tag of tag of tag...) with any of this sub-wiki's tagNames * will also be saved to this sub-wiki. Uses the in-tagtree-of filter operator. - * Only applies when creating new tiddlers, not when modifying existing ones. + * Applies when creating new tiddlers and when modifying existing ones (e.g., when tags change). */ includeTagTree: boolean; /** diff --git a/src/windows/AddWorkspace/CloneWikiForm.tsx b/src/windows/AddWorkspace/CloneWikiForm.tsx index 43692c5f..f64688f4 100644 --- a/src/windows/AddWorkspace/CloneWikiForm.tsx +++ b/src/windows/AddWorkspace/CloneWikiForm.tsx @@ -1,5 +1,5 @@ import FolderIcon from '@mui/icons-material/Folder'; -import { Autocomplete, AutocompleteRenderInputParams, Chip, MenuItem, Typography } from '@mui/material'; +import { Autocomplete, AutocompleteRenderInputParams, MenuItem, Typography } from '@mui/material'; import React from 'react'; import { useTranslation } from 'react-i18next'; @@ -91,11 +91,11 @@ export function CloneWikiForm({ form, isCreateMainWorkspace, errorInWhichCompone onChange={(_event, newValue) => { form.tagNamesSetter(newValue); }} - renderTags={(value, getTagProps) => - value.map((option, index) => { - const { key, ...tagProps } = getTagProps({ index }); - return ; - })} + slotProps={{ + chip: { + variant: 'outlined', + }, + }} renderInput={(parameters: AutocompleteRenderInputParams) => ( { tagNamesSetter(newValue); }} - renderTags={(value, getTagProps) => - value.map((option, index) => { - const { key, ...tagProps } = getTagProps({ index }); - return ; - })} + slotProps={{ + chip: { + variant: 'outlined', + }, + }} renderInput={(parameters: AutocompleteRenderInputParams) => ( { form.tagNamesSetter(newValue); }} - renderTags={(value, getTagProps) => - value.map((option, index) => { - const { key, ...tagProps } = getTagProps({ index }); - return ; - })} + slotProps={{ + chip: { + variant: 'outlined', + }, + }} renderInput={(parameters: AutocompleteRenderInputParams) => ( { 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(); }); }); diff --git a/src/windows/EditWorkspace/index.tsx b/src/windows/EditWorkspace/index.tsx index 1df470d7..31de3b63 100644 --- a/src/windows/EditWorkspace/index.tsx +++ b/src/windows/EditWorkspace/index.tsx @@ -7,7 +7,6 @@ import { Autocomplete, AutocompleteRenderInputParams, Button as ButtonRaw, - Chip, Divider, Paper, Switch, @@ -431,7 +430,47 @@ export default function EditWorkspace(): React.JSX.Element { {isSubWiki ? t('AddWorkspace.SubWorkspaceOptionsDescriptionForSub') : t('AddWorkspace.SubWorkspaceOptionsDescriptionForMain')} + { + void _event; + workspaceSetter({ ...workspace, tagNames: newValue }, true); + }} + slotProps={{ + chip: { + variant: 'outlined', + }, + }} + renderInput={(parameters: AutocompleteRenderInputParams) => ( + + )} + /> + ) => { + workspaceSetter({ ...workspace, includeTagTree: event.target.checked }, true); + }} + /> + } + > + + - {fileSystemPathFilterEnable - ? ( - ) => { - workspaceSetter({ ...workspace, fileSystemPathFilter: event.target.value || null }, true); - }} - label={t('AddWorkspace.FilterExpression')} - helperText={t('AddWorkspace.FilterExpressionHelp')} - sx={{ mb: 2 }} - /> - ) - : ( - <> - { - void _event; - workspaceSetter({ ...workspace, tagNames: newValue }, true); - }} - renderTags={(value: string[], getTagProps) => - value.map((option: string, index: number) => { - const { key, ...tagProps } = getTagProps({ index }); - return ; - })} - renderInput={(parameters: AutocompleteRenderInputParams) => ( - - )} - /> - - ) => { - workspaceSetter({ ...workspace, includeTagTree: event.target.checked }, true); - }} - /> - } - > - - - - - )} + {fileSystemPathFilterEnable && ( + ) => { + workspaceSetter({ ...workspace, fileSystemPathFilter: event.target.value || null }, true); + }} + label={t('AddWorkspace.FilterExpression')} + helperText={t('AddWorkspace.FilterExpressionHelp')} + sx={{ mb: 2 }} + /> + )} )}