From 0ce6da57cefa8f7d8b5164945b98ffea1caa104a Mon Sep 17 00:00:00 2001 From: hugo Date: Fri, 27 Feb 2026 21:47:45 +0100 Subject: [PATCH] fix: tab title for templates --- src/renderer/components/TabBar/TabBar.tsx | 103 +++++++++++++++++++++- src/renderer/i18n/locales/de.json | 1 + src/renderer/i18n/locales/en.json | 1 + src/renderer/i18n/locales/es.json | 1 + src/renderer/i18n/locales/fr.json | 1 + src/renderer/i18n/locales/it.json | 1 + tests/renderer/components/TabBar.test.tsx | 65 ++++++++++++++ 7 files changed, 172 insertions(+), 1 deletion(-) diff --git a/src/renderer/components/TabBar/TabBar.tsx b/src/renderer/components/TabBar/TabBar.tsx index 4d37c88..2c1240a 100644 --- a/src/renderer/components/TabBar/TabBar.tsx +++ b/src/renderer/components/TabBar/TabBar.tsx @@ -1,6 +1,7 @@ import React, { useRef, useState, useEffect, useCallback } from 'react'; import { useAppStore, Tab } from '../../store'; import { parseGitDiffTabId } from '../../navigation/tabPolicy'; +import { BDS_EVENT_TEMPLATES_CHANGED, addWindowEventListener } from '../../utils'; import { useI18n } from '../../i18n'; import './TabBar.css'; @@ -14,6 +15,7 @@ const getTabTitle = ( chatTitles: Map, importDefTitles: Map, commitTitles: Map, + templateTitles: Map, tr: (key: string, vars?: Record) => string, ): string => { if (tab.type === 'git-diff') { @@ -89,6 +91,10 @@ const getTabTitle = ( return scriptTitles.get(tab.id) || tr('tabBar.scripts'); } + if (tab.type === 'templates') { + return templateTitles.get(tab.id) || tr('editor.untitled'); + } + return tr('tabBar.unknown'); }; @@ -180,6 +186,12 @@ const getTabIcon = (tab: Tab): React.ReactNode => { ); + case 'templates': + return ( + + + + ); default: return ( @@ -230,6 +242,7 @@ export const TabBar: React.FC = () => { const [chatTitles, setChatTitles] = useState>(new Map()); const [importDefTitles, setImportDefTitles] = useState>(new Map()); const [commitTitles, setCommitTitles] = useState>(new Map()); + const [templateTitles, setTemplateTitles] = useState>(new Map()); // Fetch post titles from database for post tabs useEffect(() => { @@ -552,6 +565,94 @@ export const TabBar: React.FC = () => { }; }, [tabs, activeProject, tr]); + // Fetch template titles for template tabs + useEffect(() => { + const templateTabs = tabs.filter((t) => t.type === 'templates'); + const templateTabIds = new Set(templateTabs.map((t) => t.id)); + + setTemplateTitles((previous) => { + const next = new Map(previous); + let changed = false; + + for (const id of Array.from(next.keys())) { + if (!templateTabIds.has(id)) { + next.delete(id); + changed = true; + } + } + + return changed ? next : previous; + }); + + if (templateTabs.length === 0) { + return; + } + + const fetchTemplateTitles = async () => { + const newTitles = new Map(templateTitles); + let changed = false; + + for (const tab of templateTabs) { + if (templateTitles.has(tab.id)) { + continue; + } + + try { + const tmpl = await window.electronAPI?.templates.get(tab.id); + if (tmpl) { + const title = tmpl.title || tr('editor.untitled'); + if (newTitles.get(tab.id) !== title) { + newTitles.set(tab.id, title); + changed = true; + } + } + } catch (error) { + console.error(tr('tabBar.error.fetchTemplateTitle'), error); + } + } + + if (changed) { + setTemplateTitles(newTitles); + } + }; + + void fetchTemplateTitles(); + }, [tabs, tr]); // Note: intentionally not including templateTitles to avoid infinite loops + + // Listen for template updates to refresh titles + useEffect(() => { + const handleTemplatesChanged = async () => { + const templateTabs = tabs.filter((t) => t.type === 'templates'); + if (templateTabs.length === 0) { + return; + } + + const updated = new Map(templateTitles); + let changed = false; + + for (const tab of templateTabs) { + try { + const tmpl = await window.electronAPI?.templates.get(tab.id); + if (tmpl) { + const title = tmpl.title || tr('editor.untitled'); + if (updated.get(tab.id) !== title) { + updated.set(tab.id, title); + changed = true; + } + } + } catch (error) { + console.error(tr('tabBar.error.fetchTemplateTitle'), error); + } + } + + if (changed) { + setTemplateTitles(updated); + } + }; + + return addWindowEventListener(BDS_EVENT_TEMPLATES_CHANGED, handleTemplatesChanged); + }, [tabs, templateTitles, tr]); + // Check if arrows are needed based on scroll position const updateArrowVisibility = useCallback(() => { const container = tabsContainerRef.current; @@ -674,7 +775,7 @@ export const TabBar: React.FC = () => { {tabs.map((tab) => { const isActive = tab.id === activeTabId; const isDirty = tab.type === 'post' && dirtyPosts.has(tab.id); - const title = getTabTitle(tab, postTitles, media, scriptTitles, chatTitles, importDefTitles, commitTitles, tr); + const title = getTabTitle(tab, postTitles, media, scriptTitles, chatTitles, importDefTitles, commitTitles, templateTitles, tr); const icon = getTabIcon(tab); return ( diff --git a/src/renderer/i18n/locales/de.json b/src/renderer/i18n/locales/de.json index 89445c3..eef88fa 100644 --- a/src/renderer/i18n/locales/de.json +++ b/src/renderer/i18n/locales/de.json @@ -371,6 +371,7 @@ "tabBar.error.fetchChatTitle": "Chat-Titel konnte nicht geladen werden:", "tabBar.error.fetchImportTitle": "Titel der Importdefinition konnte nicht geladen werden:", "tabBar.error.fetchScriptTitle": "Skript-Titel konnte nicht geladen werden:", + "tabBar.error.fetchTemplateTitle": "Vorlagen-Titel konnte nicht geladen werden:", "tabBar.error.fetchCommitTitle": "Commit-Titel konnten nicht geladen werden:", "metadataDiff.title": "Metadaten-Diff-Werkzeug", "metadataDiff.description": "Vergleicht Beitragsmetadaten zwischen Datenbank und Markdown-Dateien. Behebt Abweichungen durch Bugs oder manuelle Änderungen.", diff --git a/src/renderer/i18n/locales/en.json b/src/renderer/i18n/locales/en.json index 4aa451b..4e3ad2f 100644 --- a/src/renderer/i18n/locales/en.json +++ b/src/renderer/i18n/locales/en.json @@ -371,6 +371,7 @@ "tabBar.error.fetchChatTitle": "Failed to fetch chat title:", "tabBar.error.fetchImportTitle": "Failed to fetch import definition title:", "tabBar.error.fetchScriptTitle": "Failed to fetch script title:", + "tabBar.error.fetchTemplateTitle": "Failed to fetch template title:", "tabBar.error.fetchCommitTitle": "Failed to fetch commit titles:", "metadataDiff.title": "Metadata Diff Tool", "metadataDiff.description": "Compare post metadata between database and markdown files. Fix inconsistencies caused by bugs or manual edits.", diff --git a/src/renderer/i18n/locales/es.json b/src/renderer/i18n/locales/es.json index ab81153..d6f5ee0 100644 --- a/src/renderer/i18n/locales/es.json +++ b/src/renderer/i18n/locales/es.json @@ -371,6 +371,7 @@ "tabBar.error.fetchChatTitle": "No se pudo cargar el título del chat:", "tabBar.error.fetchImportTitle": "No se pudo cargar el título de la definición de importación:", "tabBar.error.fetchScriptTitle": "No se pudo cargar el título del script:", + "tabBar.error.fetchTemplateTitle": "No se pudo cargar el título de la plantilla:", "tabBar.error.fetchCommitTitle": "No se pudieron cargar los títulos de los commits:", "metadataDiff.title": "Herramienta diff de metadatos", "metadataDiff.description": "Compara los metadatos de las entradas entre la base de datos y los archivos Markdown. Corrige inconsistencias causadas por errores o ediciones manuales.", diff --git a/src/renderer/i18n/locales/fr.json b/src/renderer/i18n/locales/fr.json index 92fdd9d..2b1d3c0 100644 --- a/src/renderer/i18n/locales/fr.json +++ b/src/renderer/i18n/locales/fr.json @@ -369,6 +369,7 @@ "tabBar.error.fetchChatTitle": "Impossible de charger le titre du chat :", "tabBar.error.fetchImportTitle": "Impossible de charger le titre de la définition d’import :", "tabBar.error.fetchScriptTitle": "Impossible de charger le titre du script :", + "tabBar.error.fetchTemplateTitle": "Impossible de charger le titre du modèle :", "tabBar.error.fetchCommitTitle": "Impossible de charger les titres des commits :", "metadataDiff.title": "Outil de diff des métadonnées", "metadataDiff.description": "Compare les métadonnées des articles entre la base de données et les fichiers Markdown. Corrige les incohérences causées par des bugs ou des modifications manuelles.", diff --git a/src/renderer/i18n/locales/it.json b/src/renderer/i18n/locales/it.json index defbbec..00b46ca 100644 --- a/src/renderer/i18n/locales/it.json +++ b/src/renderer/i18n/locales/it.json @@ -369,6 +369,7 @@ "tabBar.error.fetchChatTitle": "Impossibile caricare il titolo della chat:", "tabBar.error.fetchImportTitle": "Impossibile caricare il titolo della definizione di importazione:", "tabBar.error.fetchScriptTitle": "Impossibile caricare il titolo dello script:", + "tabBar.error.fetchTemplateTitle": "Impossibile caricare il titolo del modello:", "tabBar.error.fetchCommitTitle": "Impossibile caricare i titoli dei commit:", "metadataDiff.title": "Strumento diff metadati", "metadataDiff.description": "Confronta i metadati dei post tra database e file markdown. Correggi incongruenze causate da bug o modifiche manuali.", diff --git a/tests/renderer/components/TabBar.test.tsx b/tests/renderer/components/TabBar.test.tsx index c601b43..91dac4c 100644 --- a/tests/renderer/components/TabBar.test.tsx +++ b/tests/renderer/components/TabBar.test.tsx @@ -73,6 +73,10 @@ describe('TabBar', () => { ...(window as any).electronAPI?.scripts, get: vi.fn(), }, + templates: { + ...(window as any).electronAPI?.templates, + get: vi.fn(), + }, }; }); @@ -160,4 +164,65 @@ describe('TabBar', () => { expect(await screen.findByText('Publish Macro')).toBeInTheDocument(); expect((window as any).electronAPI.scripts.get).toHaveBeenCalledWith('script-1'); }); + + it('renders template title for template tab', async () => { + useAppStore.setState({ + tabs: [{ type: 'templates', id: 'template-1', isTransient: false }], + activeTabId: 'template-1', + posts: [], + media: [], + dirtyPosts: new Set(), + }); + + (window as any).electronAPI.templates.get = vi.fn().mockResolvedValue({ + id: 'template-1', + title: 'Blog Post Layout', + }); + + render(); + + expect(await screen.findByText('Blog Post Layout')).toBeInTheDocument(); + expect((window as any).electronAPI.templates.get).toHaveBeenCalledWith('template-1'); + }); + + it('updates template tab title when template changes', async () => { + useAppStore.setState({ + tabs: [{ type: 'templates', id: 'template-1', isTransient: false }], + activeTabId: 'template-1', + posts: [], + media: [], + dirtyPosts: new Set(), + }); + + (window as any).electronAPI.templates.get = vi.fn().mockResolvedValue({ + id: 'template-1', + title: 'Old Title', + }); + + // Capture the bds:templates-changed listener + let templatesChangedHandler: (() => void) | null = null; + (window as any).addEventListener = vi.fn((event: string, handler: () => void) => { + if (event === 'bds:templates-changed') { + templatesChangedHandler = handler; + } + }); + (window as any).removeEventListener = vi.fn(); + + render(); + + expect(await screen.findByText('Old Title')).toBeInTheDocument(); + + // Now simulate the template being updated + (window as any).electronAPI.templates.get = vi.fn().mockResolvedValue({ + id: 'template-1', + title: 'New Title', + }); + + // Trigger the templates-changed event + await act(async () => { + templatesChangedHandler?.(); + }); + + expect(await screen.findByText('New Title')).toBeInTheDocument(); + }); });