feat: user-managed templates
This commit is contained in:
@@ -36,6 +36,12 @@ const ScriptsIcon = () => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
const TemplatesIcon = () => (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M4 4h7v7H4V4zm9 0h7v7h-7V4zM4 13h7v7H4v-7zm9 0h7v7h-7v-7zM5.5 5.5v4h4v-4h-4zm9 0v4h4v-4h-4zm-9 9v4h4v-4h-4zm9 0v4h4v-4h-4z"/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
const SettingsIcon = () => (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19.14 12.94c.04-.31.06-.63.06-.94 0-.31-.02-.63-.06-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.04.31-.06.63-.06.94s.02.63.06.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/>
|
||||
@@ -183,6 +189,13 @@ export const ActivityBar: React.FC = () => {
|
||||
>
|
||||
<ScriptsIcon />
|
||||
</button>
|
||||
<button
|
||||
className={`activity-bar-item ${isActivityActive(snapshot, 'templates') ? 'active' : ''}`}
|
||||
onClick={() => executeActivityClick('templates')}
|
||||
title={getTitle('templates')}
|
||||
>
|
||||
<TemplatesIcon />
|
||||
</button>
|
||||
<button
|
||||
className={`activity-bar-item ${isActivityActive(snapshot, 'tags') ? 'active' : ''}`}
|
||||
onClick={() => executeActivityClick('tags')}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { GitDiffView } from '../GitDiffView/GitDiffView';
|
||||
import { DocumentationView } from '../DocumentationView/DocumentationView';
|
||||
import { SiteValidationView } from '../SiteValidationView';
|
||||
import { ScriptsView } from '../ScriptsView/ScriptsView';
|
||||
import { TemplatesView } from '../TemplatesView/TemplatesView';
|
||||
import { AutoSaveManager, getContrastColor, loadTagColorMap } from '../../utils';
|
||||
import { InsertModal } from '../InsertModal';
|
||||
import { AISuggestionsModal, AISuggestions } from '../AISuggestionsModal/AISuggestionsModal';
|
||||
@@ -71,6 +72,9 @@ const autoSaveManager = new AutoSaveManager({
|
||||
if ('categories' in changes) {
|
||||
update.categories = changes.categories as string[];
|
||||
}
|
||||
if ('templateSlug' in changes) {
|
||||
(update as Record<string, unknown>).templateSlug = changes.templateSlug as string || null;
|
||||
}
|
||||
|
||||
const updated = await window.electronAPI?.posts.update(id, update);
|
||||
if (updated) {
|
||||
@@ -191,6 +195,8 @@ export const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
||||
const [author, setAuthor] = useState('');
|
||||
const [tags, setTags] = useState<string[]>([]);
|
||||
const [selectedCategories, setSelectedCategories] = useState<string[]>(['article']);
|
||||
const [templateSlug, setTemplateSlug] = useState('');
|
||||
const [availablePostTemplates, setAvailablePostTemplates] = useState<Array<{ slug: string; title: string }>>([]);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [hasPublishedVersion, setHasPublishedVersion] = useState(false);
|
||||
const [editorMode, setEditorMode] = useState<EditorMode>(preferredEditorMode);
|
||||
@@ -319,10 +325,15 @@ export const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
||||
setAuthor(post.author || '');
|
||||
setTags(post.tags);
|
||||
setSelectedCategories(post.categories.length > 0 ? post.categories : ['article']);
|
||||
setTemplateSlug((post as PostData & { templateSlug?: string }).templateSlug || '');
|
||||
setMetadataExpanded(post.title === '');
|
||||
markClean(postId);
|
||||
// Mark as initialized AFTER setting local state
|
||||
setIsInitialized(true);
|
||||
// Load available post templates for the dropdown
|
||||
window.electronAPI?.templates.getEnabledByKind('post').then((templates) => {
|
||||
setAvailablePostTemplates((templates ?? []).map((tmpl) => ({ slug: tmpl.slug, title: tmpl.title })));
|
||||
});
|
||||
}
|
||||
}, [post, postId, markClean, isInitialized]);
|
||||
|
||||
@@ -335,7 +346,8 @@ export const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
||||
const contentChanged = content !== post.content;
|
||||
const titleChanged = title !== post.title;
|
||||
const authorChanged = author !== (post.author || '');
|
||||
const hasChanges = contentChanged || titleChanged || authorChanged ||
|
||||
const templateSlugChanged = templateSlug !== ((post as PostData & { templateSlug?: string }).templateSlug || '');
|
||||
const hasChanges = contentChanged || titleChanged || authorChanged || templateSlugChanged ||
|
||||
JSON.stringify(tags.slice().sort()) !== JSON.stringify(post.tags.slice().sort()) ||
|
||||
JSON.stringify(selectedCategories.slice().sort()) !== JSON.stringify(post.categories.slice().sort());
|
||||
|
||||
@@ -349,11 +361,12 @@ export const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
||||
author,
|
||||
tags: tags.join(', '),
|
||||
categories: selectedCategories,
|
||||
templateSlug: templateSlug || undefined,
|
||||
});
|
||||
} else {
|
||||
markClean(postId);
|
||||
}
|
||||
}, [title, content, author, tags, selectedCategories, post, postId, isInitialized, isDirty, markDirty, markClean]);
|
||||
}, [title, content, author, tags, selectedCategories, templateSlug, post, postId, isInitialized, isDirty, markDirty, markClean]);
|
||||
|
||||
// Handle editor mode change and persist preference
|
||||
const handleEditorModeChange = (mode: EditorMode) => {
|
||||
@@ -375,7 +388,8 @@ export const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
||||
author: author || undefined,
|
||||
tags,
|
||||
categories: selectedCategories.length > 0 ? selectedCategories : ['article'],
|
||||
});
|
||||
templateSlug: templateSlug || null,
|
||||
} as Parameters<typeof window.electronAPI.posts.update>[1]);
|
||||
|
||||
if (updated) {
|
||||
updatePost(postId, updated as Partial<PostData>);
|
||||
@@ -799,6 +813,20 @@ export const PostEditor: React.FC<PostEditorProps> = ({ postId }) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{availablePostTemplates.length > 0 && (
|
||||
<div className="editor-field">
|
||||
<label>{tr('editor.field.template')}</label>
|
||||
<select
|
||||
value={templateSlug}
|
||||
onChange={(e) => setTemplateSlug(e.target.value)}
|
||||
>
|
||||
<option value="">{tr('editor.field.templateDefault')}</option>
|
||||
{availablePostTemplates.map((tmpl) => (
|
||||
<option key={tmpl.slug} value={tmpl.slug}>{tmpl.title}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<PostLinks
|
||||
postId={postId}
|
||||
@@ -1836,6 +1864,7 @@ export const Editor: React.FC = () => {
|
||||
),
|
||||
'site-validation': () => <SiteValidationView />,
|
||||
scripts: () => <ScriptsView scriptId={editorRoute.tabId} />,
|
||||
templates: () => <TemplatesView templateId={editorRoute.tabId} />,
|
||||
post: () => (editorRoute.tabId ? <PostEditor key={editorRoute.tabId} postId={editorRoute.tabId} /> : <Dashboard />),
|
||||
media: () => (editorRoute.tabId ? <MediaEditor key={editorRoute.tabId} mediaId={editorRoute.tabId} /> : <Dashboard />),
|
||||
dashboard: () => <Dashboard />,
|
||||
|
||||
@@ -34,6 +34,8 @@ interface CategoryMetadata {
|
||||
renderInLists: boolean;
|
||||
showTitle: boolean;
|
||||
title: string;
|
||||
postTemplateSlug?: string;
|
||||
listTemplateSlug?: string;
|
||||
}
|
||||
|
||||
const RENDER_LANGUAGE_LABEL_KEY: Record<SupportedLanguage, string> = {
|
||||
@@ -151,6 +153,10 @@ export const SettingsView: React.FC = () => {
|
||||
const [categoryMetadata, setCategoryMetadata] = useState<Record<string, CategoryMetadata>>(DEFAULT_CATEGORY_METADATA);
|
||||
const [newCategoryInput, setNewCategoryInput] = useState('');
|
||||
|
||||
// Available templates for category dropdowns
|
||||
const [postTemplates, setPostTemplates] = useState<Array<{ slug: string; title: string }>>([]);
|
||||
const [listTemplates, setListTemplates] = useState<Array<{ slug: string; title: string }>>([]);
|
||||
|
||||
// AI Assistant settings
|
||||
const [aiSystemPrompt, setAiSystemPrompt] = useState('');
|
||||
const [aiSystemPromptModified, setAiSystemPromptModified] = useState(false);
|
||||
@@ -221,6 +227,8 @@ export const SettingsView: React.FC = () => {
|
||||
title: typeof (settings as any)?.title === 'string' && (settings as any).title.trim().length > 0
|
||||
? (settings as any).title.trim()
|
||||
: category,
|
||||
postTemplateSlug: typeof (settings as any)?.postTemplateSlug === 'string' ? (settings as any).postTemplateSlug : undefined,
|
||||
listTemplateSlug: typeof (settings as any)?.listTemplateSlug === 'string' ? (settings as any).listTemplateSlug : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -230,6 +238,18 @@ export const SettingsView: React.FC = () => {
|
||||
}
|
||||
}, [activeProject]);
|
||||
|
||||
// Load available templates for category dropdowns
|
||||
useEffect(() => {
|
||||
if (activeProject) {
|
||||
window.electronAPI?.templates.getEnabledByKind('post').then((templates) => {
|
||||
setPostTemplates(templates.map((t) => ({ slug: t.slug, title: t.title })));
|
||||
});
|
||||
window.electronAPI?.templates.getEnabledByKind('list').then((templates) => {
|
||||
setListTemplates(templates.map((t) => ({ slug: t.slug, title: t.title })));
|
||||
});
|
||||
}
|
||||
}, [activeProject]);
|
||||
|
||||
// Load saved credentials and categories
|
||||
useEffect(() => {
|
||||
const loadSettings = async () => {
|
||||
@@ -771,6 +791,29 @@ export const SettingsView: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleCategoryTemplateChange = async (
|
||||
category: string,
|
||||
field: 'postTemplateSlug' | 'listTemplateSlug',
|
||||
value: string,
|
||||
) => {
|
||||
const nextCategoryMetadata: Record<string, CategoryMetadata> = {
|
||||
...categoryMetadata,
|
||||
[category]: {
|
||||
...(categoryMetadata[category] || { renderInLists: true, showTitle: true, title: category }),
|
||||
[field]: value || undefined,
|
||||
},
|
||||
};
|
||||
|
||||
setCategoryMetadata(nextCategoryMetadata);
|
||||
|
||||
try {
|
||||
await window.electronAPI?.meta.updateProjectMetadata({ categoryMetadata: nextCategoryMetadata });
|
||||
} catch (error) {
|
||||
console.error('Failed to update category settings:', error);
|
||||
showToast.error(t('settings.toast.categorySettingsUpdateFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
const renderContentSettings = () => (
|
||||
<SettingSection
|
||||
id="settings-section-content"
|
||||
@@ -786,6 +829,8 @@ export const SettingsView: React.FC = () => {
|
||||
<th>{t('settings.content.titleColumn')}</th>
|
||||
<th>{t('settings.content.renderInLists')}</th>
|
||||
<th>{t('settings.content.showTitles')}</th>
|
||||
<th>{t('settings.content.postTemplateColumn')}</th>
|
||||
<th>{t('settings.content.listTemplateColumn')}</th>
|
||||
<th>{t('settings.content.actionsColumn')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -823,6 +868,30 @@ export const SettingsView: React.FC = () => {
|
||||
onChange={(event) => handleCategorySettingToggle(cat, 'showTitle', event.target.checked)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
value={metadata.postTemplateSlug || ''}
|
||||
onChange={(event) => handleCategoryTemplateChange(cat, 'postTemplateSlug', event.target.value)}
|
||||
aria-label={t('settings.content.postTemplateAria', { category: cat })}
|
||||
>
|
||||
<option value="">{t('editor.field.templateDefault')}</option>
|
||||
{postTemplates.map((tpl) => (
|
||||
<option key={tpl.slug} value={tpl.slug}>{tpl.title}</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<select
|
||||
value={metadata.listTemplateSlug || ''}
|
||||
onChange={(event) => handleCategoryTemplateChange(cat, 'listTemplateSlug', event.target.value)}
|
||||
aria-label={t('settings.content.listTemplateAria', { category: cat })}
|
||||
>
|
||||
<option value="">{t('editor.field.templateDefault')}</option>
|
||||
{listTemplates.map((tpl) => (
|
||||
<option key={tpl.slug} value={tpl.slug}>{tpl.title}</option>
|
||||
))}
|
||||
</select>
|
||||
</td>
|
||||
<td className="category-actions-cell">
|
||||
{!isProtected && (
|
||||
<button
|
||||
@@ -1213,6 +1282,29 @@ export const SettingsView: React.FC = () => {
|
||||
</button>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
id="rebuild-templates"
|
||||
label={t('settings.data.rebuildTemplatesLabel')}
|
||||
description={t('settings.data.rebuildTemplatesDescription')}
|
||||
>
|
||||
<button
|
||||
className="secondary"
|
||||
onClick={async () => {
|
||||
showToast.loading(t('settings.toast.rebuildTemplatesLoading'));
|
||||
try {
|
||||
await window.electronAPI?.templates.rebuildFromFiles();
|
||||
showToast.dismiss();
|
||||
showToast.success(t('settings.toast.rebuildTemplatesSuccess'));
|
||||
} catch {
|
||||
showToast.dismiss();
|
||||
showToast.error(t('settings.toast.rebuildTemplatesFailed'));
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t('settings.data.rebuildTemplatesAction')}
|
||||
</button>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
id="rebuild-links"
|
||||
label={t('settings.data.rebuildLinksLabel')}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import { useAppStore, PostData, MediaData } from '../../store';
|
||||
import { showToast } from '../Toast';
|
||||
import { BDS_EVENT_SCRIPTS_CHANGED, dispatchWindowEvent, getContrastColor, groupPostsByStatus, loadTagColorMap } from '../../utils';
|
||||
import { BDS_EVENT_SCRIPTS_CHANGED, BDS_EVENT_TEMPLATES_CHANGED, dispatchWindowEvent, getContrastColor, groupPostsByStatus, loadTagColorMap } from '../../utils';
|
||||
import type { ChatConversation, ImportDefinitionData } from '../../types/electron';
|
||||
import { GitSidebar } from '../GitSidebar/GitSidebar';
|
||||
import { scrollToSettingsSection, SettingsCategory } from '../SettingsView/SettingsView';
|
||||
import { scrollToTagsSection, TagsCategory } from '../TagsView';
|
||||
import { activateSidebarSection } from '../../navigation/sectionActivation';
|
||||
import { getPersistedSidebarSection, setPersistedSidebarSection } from '../../navigation/sidebarUiPersistence';
|
||||
import { openChatTab, openEntityTab, openImportTab, openScriptTab, openSingletonToolTab } from '../../navigation/tabPolicy';
|
||||
import { openChatTab, openEntityTab, openImportTab, openScriptTab, openTemplateTab, openSingletonToolTab } from '../../navigation/tabPolicy';
|
||||
import { createAndFocusPost } from '../../navigation/postCreation';
|
||||
import type { SidebarView } from '../../navigation/sidebarViewRegistry';
|
||||
import { useI18n } from '../../i18n';
|
||||
@@ -1702,6 +1702,120 @@ const ScriptsList: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const TemplatesList: React.FC = () => {
|
||||
const { t, language } = useI18n();
|
||||
const { openTab, activeTabId, closeTab } = useAppStore();
|
||||
const activeProjectId = useAppStore((state) => state.activeProject?.id);
|
||||
|
||||
const loadTemplates = useCallback(async (): Promise<Array<{ id: string; title: string; updatedAt: string }>> => {
|
||||
const items = await window.electronAPI?.templates.getAll();
|
||||
return (items ?? []).map((item) => ({ id: item.id, title: item.title, updatedAt: item.updatedAt }));
|
||||
}, []);
|
||||
|
||||
const {
|
||||
items: templates,
|
||||
setItems: setTemplates,
|
||||
isLoading,
|
||||
reload: reloadTemplates,
|
||||
} = useProjectScopedSidebarData<Array<{ id: string; title: string; updatedAt: string }>[number]>({
|
||||
load: loadTemplates,
|
||||
activeProjectId,
|
||||
refreshEventName: BDS_EVENT_TEMPLATES_CHANGED,
|
||||
});
|
||||
|
||||
const handleCreateTemplate = async () => {
|
||||
try {
|
||||
const created = await window.electronAPI?.templates.create({
|
||||
title: t('sidebar.templates.newTemplate'),
|
||||
kind: 'post',
|
||||
content: '',
|
||||
enabled: true,
|
||||
});
|
||||
|
||||
if (!created) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTemplates((prev) => [
|
||||
{ id: created.id, title: created.title, updatedAt: created.updatedAt },
|
||||
...prev.filter((tmpl) => tmpl.id !== created.id),
|
||||
]);
|
||||
dispatchWindowEvent(BDS_EVENT_TEMPLATES_CHANGED);
|
||||
openTemplateTab(openTab, created.id, 'pin');
|
||||
void reloadTemplates();
|
||||
} catch (error) {
|
||||
console.error('Failed to create template:', error);
|
||||
showToast.error(t('sidebar.templates.createFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteTemplate = async (event: React.MouseEvent, templateId: string) => {
|
||||
event.stopPropagation();
|
||||
try {
|
||||
const deleted = await window.electronAPI?.templates.delete(templateId);
|
||||
if (!deleted) {
|
||||
showToast.error(t('sidebar.templates.deleteFailed'));
|
||||
return;
|
||||
}
|
||||
setTemplates((prev) => prev.filter((tmpl) => tmpl.id !== templateId));
|
||||
closeTab(templateId);
|
||||
dispatchWindowEvent(BDS_EVENT_TEMPLATES_CHANGED);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete template:', error);
|
||||
showToast.error(t('sidebar.templates.deleteFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SidebarEntityList
|
||||
header={t('sidebar.templates.header')}
|
||||
createTitle={t('sidebar.templates.newTemplate')}
|
||||
onCreate={handleCreateTemplate}
|
||||
isLoading={isLoading}
|
||||
loadingLabel={t('sidebar.loading')}
|
||||
emptyMessage={t('sidebar.templates.none')}
|
||||
emptyActionLabel={t('sidebar.templates.createTemplate')}
|
||||
onEmptyAction={handleCreateTemplate}
|
||||
items={templates}
|
||||
getItemKey={(tmpl) => tmpl.id}
|
||||
renderItem={(tmpl) => (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-label={tmpl.title}
|
||||
className={`chat-list-item ${activeTabId === tmpl.id ? 'active' : ''}`}
|
||||
onClick={() => openTemplateTab(openTab, tmpl.id, 'preview')}
|
||||
onDoubleClick={() => openTemplateTab(openTab, tmpl.id, 'pin')}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
openTemplateTab(openTab, tmpl.id, 'pin');
|
||||
return;
|
||||
}
|
||||
if (event.key === ' ') {
|
||||
event.preventDefault();
|
||||
openTemplateTab(openTab, tmpl.id, 'preview');
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="chat-item-content">
|
||||
<div className="chat-item-title">{tmpl.title}</div>
|
||||
<div className="chat-item-date">
|
||||
{formatSidebarRelativeDate({ dateString: tmpl.updatedAt, language, t })}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="chat-item-delete"
|
||||
onClick={(event) => handleDeleteTemplate(event, tmpl.id)}
|
||||
title={t('sidebar.templates.deleteTemplate')}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sidebar: React.FC = () => {
|
||||
const { activeView, sidebarVisible } = useAppStore();
|
||||
|
||||
@@ -1714,6 +1828,7 @@ export const Sidebar: React.FC = () => {
|
||||
pages: <PostsList mode="pages" isActive={true} />,
|
||||
media: <MediaList />,
|
||||
scripts: <ScriptsList />,
|
||||
templates: <TemplatesList />,
|
||||
settings: <SettingsNav />,
|
||||
tags: <TagsNav />,
|
||||
chat: <ChatList />,
|
||||
|
||||
@@ -18,6 +18,7 @@ interface TagData {
|
||||
projectId: string;
|
||||
name: string;
|
||||
color?: string;
|
||||
postTemplateSlug?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -147,6 +148,8 @@ export const TagsView: React.FC = () => {
|
||||
const [editingTagId, setEditingTagId] = useState<string | null>(null);
|
||||
const [editTagColor, setEditTagColor] = useState<string>('');
|
||||
const [editTagName, setEditTagName] = useState('');
|
||||
const [editTagTemplate, setEditTagTemplate] = useState<string>('');
|
||||
const [postTemplates, setPostTemplates] = useState<Array<{ slug: string; title: string }>>([]);
|
||||
|
||||
// Merge tags state
|
||||
const [mergeTargetName, setMergeTargetName] = useState('');
|
||||
@@ -188,6 +191,13 @@ export const TagsView: React.FC = () => {
|
||||
});
|
||||
}, [loadTags]);
|
||||
|
||||
// Load post templates on mount
|
||||
useEffect(() => {
|
||||
window.electronAPI?.templates.getEnabledByKind('post').then((templates) => {
|
||||
setPostTemplates(templates.map((t) => ({ slug: t.slug, title: t.title })));
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Handle tag selection
|
||||
const handleTagSelect = (name: string) => {
|
||||
setSelectedTags(prev => {
|
||||
@@ -247,6 +257,7 @@ export const TagsView: React.FC = () => {
|
||||
setEditingTagId(tag.id);
|
||||
setEditTagColor(tag.color || '');
|
||||
setEditTagName(tag.name);
|
||||
setEditTagTemplate(tag.postTemplateSlug || '');
|
||||
};
|
||||
|
||||
// Save tag edit
|
||||
@@ -254,9 +265,10 @@ export const TagsView: React.FC = () => {
|
||||
if (!editingTagId) return;
|
||||
|
||||
try {
|
||||
// Update color
|
||||
// Update color and template
|
||||
await window.electronAPI?.tags.update(editingTagId, {
|
||||
color: editTagColor || null,
|
||||
postTemplateSlug: editTagTemplate || null,
|
||||
});
|
||||
|
||||
// If name changed, rename the tag
|
||||
@@ -455,6 +467,7 @@ export const TagsView: React.FC = () => {
|
||||
<div className="tag-edit-form">
|
||||
<h4>{t('tagsView.edit.title', { name: selectedTagObjects[0].name })}</h4>
|
||||
{editingTagId === selectedTagObjects[0].id ? (
|
||||
<>
|
||||
<div className="tag-form-row">
|
||||
<input
|
||||
type="text"
|
||||
@@ -469,8 +482,8 @@ export const TagsView: React.FC = () => {
|
||||
onChange={(e) => setEditTagColor(e.target.value)}
|
||||
/>
|
||||
{editTagColor && (
|
||||
<button
|
||||
className="clear-color"
|
||||
<button
|
||||
className="clear-color"
|
||||
onClick={() => setEditTagColor('')}
|
||||
title={t('tagsView.removeColor')}
|
||||
>
|
||||
@@ -481,6 +494,14 @@ export const TagsView: React.FC = () => {
|
||||
<button onClick={handleSaveEdit} className="primary">{t('common.save')}</button>
|
||||
<button onClick={() => setEditingTagId(null)}>{t('common.cancel')}</button>
|
||||
</div>
|
||||
<div className="tagsview-field">
|
||||
<label>{t('tagsView.edit.postTemplate')}</label>
|
||||
<select value={editTagTemplate} onChange={(e) => setEditTagTemplate(e.target.value)}>
|
||||
<option value="">{t('editor.field.templateDefault')}</option>
|
||||
{postTemplates.map(tmpl => <option key={tmpl.slug} value={tmpl.slug}>{tmpl.title}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="tag-form-row">
|
||||
<span className="tag-preview" style={
|
||||
|
||||
54
src/renderer/components/TemplatesView/TemplatesView.css
Normal file
54
src/renderer/components/TemplatesView/TemplatesView.css
Normal file
@@ -0,0 +1,54 @@
|
||||
.templates-view-shell {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.templates-view {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.templates-meta-row {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.templates-enabled-field {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.templates-enabled-field label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.templates-editor {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.templates-toolbar {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.templates-monaco {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background-color: var(--vscode-input-background);
|
||||
}
|
||||
|
||||
.templates-save-button {
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.templates-validate-button {
|
||||
padding: 4px 12px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
360
src/renderer/components/TemplatesView/TemplatesView.tsx
Normal file
360
src/renderer/components/TemplatesView/TemplatesView.tsx
Normal file
@@ -0,0 +1,360 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import MonacoEditor from '@monaco-editor/react';
|
||||
import type { TemplateData, TemplateKind } from '../../../main/shared/electronApi';
|
||||
import { useAppStore } from '../../store';
|
||||
import { BDS_EVENT_TEMPLATES_CHANGED, dispatchWindowEvent } from '../../utils';
|
||||
import { useI18n } from '../../i18n';
|
||||
import { showToast } from '../Toast';
|
||||
import './TemplatesView.css';
|
||||
|
||||
const UI_DATE_LOCALE: Record<string, string> = {
|
||||
en: 'en-US',
|
||||
de: 'de-DE',
|
||||
fr: 'fr-FR',
|
||||
it: 'it-IT',
|
||||
es: 'es-ES',
|
||||
};
|
||||
|
||||
const toTemplateSlug = (value: string) => {
|
||||
const normalized = value
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '');
|
||||
return normalized || 'template';
|
||||
};
|
||||
|
||||
interface TemplatesViewProps {
|
||||
templateId: string | null;
|
||||
}
|
||||
|
||||
export const TemplatesView: React.FC<TemplatesViewProps> = ({ templateId }) => {
|
||||
const { t, language } = useI18n();
|
||||
const closeTab = useAppStore((state) => state.closeTab);
|
||||
const [template, setTemplate] = useState<TemplateData | null>(null);
|
||||
const [title, setTitle] = useState('');
|
||||
const [slug, setSlug] = useState('');
|
||||
const [kind, setKind] = useState<TemplateKind>('post');
|
||||
const [enabled, setEnabled] = useState(true);
|
||||
const [templateContent, setTemplateContent] = useState('');
|
||||
const [isSlugManuallyEdited, setIsSlugManuallyEdited] = useState(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
const [monacoResetToken, setMonacoResetToken] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
const loadTemplate = async () => {
|
||||
if (!templateId) {
|
||||
setTemplate(null);
|
||||
setTitle('');
|
||||
setSlug('');
|
||||
setKind('post');
|
||||
setEnabled(true);
|
||||
setTemplateContent('');
|
||||
setMonacoResetToken((prev) => prev + 1);
|
||||
setIsSlugManuallyEdited(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const item = await window.electronAPI?.templates.get(templateId);
|
||||
if (cancelled || !item) {
|
||||
setTemplate(null);
|
||||
setTitle('');
|
||||
setSlug('');
|
||||
setKind('post');
|
||||
setEnabled(true);
|
||||
setTemplateContent('');
|
||||
setMonacoResetToken((prev) => prev + 1);
|
||||
setIsSlugManuallyEdited(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setTemplate(item);
|
||||
setTitle(item.title || '');
|
||||
setSlug(item.slug || toTemplateSlug(item.title || ''));
|
||||
setKind(item.kind || 'post');
|
||||
setEnabled(item.enabled ?? true);
|
||||
setTemplateContent(item.content || '');
|
||||
setMonacoResetToken((prev) => prev + 1);
|
||||
const normalizedExisting = toTemplateSlug(item.slug || item.title || '');
|
||||
setIsSlugManuallyEdited(normalizedExisting !== toTemplateSlug(item.title || ''));
|
||||
};
|
||||
|
||||
void loadTemplate();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [templateId]);
|
||||
|
||||
const hasChanges =
|
||||
!!template &&
|
||||
(title !== template.title ||
|
||||
slug !== template.slug ||
|
||||
kind !== template.kind ||
|
||||
enabled !== template.enabled ||
|
||||
templateContent !== template.content);
|
||||
|
||||
const handleTitleChange = (nextTitle: string) => {
|
||||
setTitle(nextTitle);
|
||||
if (!isSlugManuallyEdited) {
|
||||
setSlug(toTemplateSlug(nextTitle));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSlugChange = (nextSlug: string) => {
|
||||
setIsSlugManuallyEdited(true);
|
||||
setSlug(toTemplateSlug(nextSlug));
|
||||
};
|
||||
|
||||
const handleValidate = async (options: { notify: boolean } = { notify: true }): Promise<boolean> => {
|
||||
if (!template || isValidating) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setIsValidating(true);
|
||||
try {
|
||||
const result = await window.electronAPI?.templates.validate(templateContent);
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!result.valid) {
|
||||
if (options.notify) {
|
||||
showToast.error(t('templates.validate.invalid', { count: result.errors.length }));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.notify) {
|
||||
showToast.success(t('templates.validate.valid'));
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
setIsValidating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveTemplate = async () => {
|
||||
if (!template || isSaving || !hasChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const isValid = await handleValidate({ notify: true });
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updated = await window.electronAPI?.templates.update(template.id, {
|
||||
title,
|
||||
slug,
|
||||
kind,
|
||||
enabled,
|
||||
content: templateContent,
|
||||
});
|
||||
|
||||
if (!updated) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTemplate(updated);
|
||||
setTitle(updated.title || '');
|
||||
setSlug(updated.slug || toTemplateSlug(updated.title || ''));
|
||||
setKind(updated.kind || 'post');
|
||||
setEnabled(updated.enabled ?? true);
|
||||
setTemplateContent(updated.content || '');
|
||||
const normalizedExisting = toTemplateSlug(updated.slug || updated.title || '');
|
||||
setIsSlugManuallyEdited(normalizedExisting !== toTemplateSlug(updated.title || ''));
|
||||
dispatchWindowEvent(BDS_EVENT_TEMPLATES_CHANGED);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteTemplate = async () => {
|
||||
if (!template) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const deleted = await window.electronAPI?.templates.delete(template.id);
|
||||
if (!deleted) {
|
||||
showToast.error(t('sidebar.templates.deleteFailed'));
|
||||
return;
|
||||
}
|
||||
closeTab(template.id);
|
||||
dispatchWindowEvent(BDS_EVENT_TEMPLATES_CHANGED);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete template:', error);
|
||||
showToast.error(t('sidebar.templates.deleteFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
|
||||
event.preventDefault();
|
||||
void handleSaveTemplate();
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof window.addEventListener !== 'function' || typeof window.removeEventListener !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [handleSaveTemplate]);
|
||||
|
||||
return (
|
||||
<div className="templates-view-shell">
|
||||
<div className="editor-header templates-header">
|
||||
<div className="editor-tabs">
|
||||
<div className="editor-tab active">
|
||||
<span className="editor-tab-title">{title || t('editor.untitled')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="editor-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="templates-save-button"
|
||||
onClick={handleSaveTemplate}
|
||||
disabled={!template || !hasChanges || isSaving}
|
||||
>
|
||||
{isSaving ? t('editor.saving') : t('templates.save')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="templates-validate-button"
|
||||
onClick={() => {
|
||||
void handleValidate({ notify: true });
|
||||
}}
|
||||
disabled={!template || isValidating || isSaving}
|
||||
>
|
||||
{isValidating ? t('templates.validate.checking') : t('templates.validate')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary danger"
|
||||
onClick={handleDeleteTemplate}
|
||||
disabled={!template}
|
||||
title={t('templates.delete')}
|
||||
>
|
||||
{t('templates.delete')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="editor-content templates-view">
|
||||
<div className="editor-header-row templates-meta-row">
|
||||
<div className="editor-meta">
|
||||
<div className="editor-field-row">
|
||||
<div className="editor-field">
|
||||
<label htmlFor="template-title">{t('editor.field.title')}</label>
|
||||
<input
|
||||
id="template-title"
|
||||
type="text"
|
||||
value={title}
|
||||
onChange={(event) => handleTitleChange(event.target.value)}
|
||||
disabled={!template}
|
||||
/>
|
||||
</div>
|
||||
<div className="editor-field">
|
||||
<label htmlFor="template-slug">{t('editor.field.slug')}</label>
|
||||
<input
|
||||
id="template-slug"
|
||||
type="text"
|
||||
value={slug}
|
||||
onChange={(event) => handleSlugChange(event.target.value)}
|
||||
disabled={!template}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="editor-field-row">
|
||||
<div className="editor-field">
|
||||
<label htmlFor="template-kind">{t('templates.field.kind')}</label>
|
||||
<select
|
||||
id="template-kind"
|
||||
value={kind}
|
||||
onChange={(event) => setKind(event.target.value as TemplateKind)}
|
||||
disabled={!template}
|
||||
>
|
||||
<option value="post">{t('templates.kind.post')}</option>
|
||||
<option value="list">{t('templates.kind.list')}</option>
|
||||
<option value="not-found">{t('templates.kind.not_found')}</option>
|
||||
<option value="partial">{t('templates.kind.partial')}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="editor-field templates-enabled-field">
|
||||
<label htmlFor="template-enabled">
|
||||
<input
|
||||
id="template-enabled"
|
||||
type="checkbox"
|
||||
checked={enabled}
|
||||
onChange={(event) => setEnabled(event.target.checked)}
|
||||
disabled={!template}
|
||||
/>
|
||||
{t('templates.field.enabled')}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="editor-body templates-editor">
|
||||
<div className="editor-toolbar templates-toolbar">
|
||||
<div className="editor-toolbar-left">
|
||||
<label>{t('templates.content')}</label>
|
||||
</div>
|
||||
<div className="editor-toolbar-center" />
|
||||
<div className="editor-toolbar-right" />
|
||||
</div>
|
||||
|
||||
<div className="templates-monaco">
|
||||
<MonacoEditor
|
||||
key={monacoResetToken}
|
||||
height="100%"
|
||||
language="html"
|
||||
theme="vs-dark"
|
||||
defaultValue={templateContent}
|
||||
onChange={(value) => setTemplateContent(value || '')}
|
||||
options={{
|
||||
minimap: { enabled: false },
|
||||
wordWrap: 'on',
|
||||
lineNumbers: 'on',
|
||||
fontSize: 14,
|
||||
fontFamily: "'Cascadia Code', 'Consolas', 'Courier New', monospace",
|
||||
padding: { top: 12, bottom: 12 },
|
||||
automaticLayout: true,
|
||||
scrollBeyondLastLine: false,
|
||||
renderLineHighlight: 'line',
|
||||
formatOnPaste: true,
|
||||
cursorStyle: 'line',
|
||||
cursorBlinking: 'smooth',
|
||||
readOnly: !template,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{template && (
|
||||
<div className="editor-footer">
|
||||
<span className="text-muted text-small">
|
||||
{t('editor.footer.created')}:{' '}
|
||||
{new Date(template.createdAt).toLocaleString(UI_DATE_LOCALE[language] || UI_DATE_LOCALE.en)}
|
||||
</span>
|
||||
<span className="text-muted text-small">
|
||||
{t('editor.footer.updated')}:{' '}
|
||||
{new Date(template.updatedAt).toLocaleString(UI_DATE_LOCALE[language] || UI_DATE_LOCALE.en)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,7 @@
|
||||
"activity.media": "Medien",
|
||||
"activity.scripts": "Skripte",
|
||||
"activity.tags": "Schlagwörter",
|
||||
"activity.templates": "Vorlagen",
|
||||
"activity.aiAssistant": "KI-Assistent",
|
||||
"activity.import": "Importieren",
|
||||
"activity.sourceControl": "Versionskontrolle",
|
||||
@@ -179,6 +180,9 @@
|
||||
"settings.toast.rebuildScriptsLoading": "Skriptdatenbank wird neu aufgebaut...",
|
||||
"settings.toast.rebuildScriptsSuccess": "Skriptdatenbank neu aufgebaut",
|
||||
"settings.toast.rebuildScriptsFailed": "Skriptdatenbank konnte nicht neu aufgebaut werden",
|
||||
"settings.toast.rebuildTemplatesLoading": "Vorlagen-Datenbank wird neu aufgebaut...",
|
||||
"settings.toast.rebuildTemplatesSuccess": "Vorlagen-Datenbank wurde neu aufgebaut",
|
||||
"settings.toast.rebuildTemplatesFailed": "Fehler beim Neuaufbau der Vorlagen-Datenbank",
|
||||
"settings.toast.rebuildLinksLoading": "Beitragslinks werden neu aufgebaut...",
|
||||
"settings.toast.rebuildLinksSuccess": "Beitragslinks neu aufgebaut",
|
||||
"settings.toast.rebuildLinksFailed": "Beitragslinks konnten nicht neu aufgebaut werden",
|
||||
@@ -451,6 +455,19 @@
|
||||
"scripts.kind.utility": "utility",
|
||||
"scripts.kind.macro": "macro",
|
||||
"scripts.kind.transform": "transform",
|
||||
"templates.save": "Vorlage speichern",
|
||||
"templates.delete": "Vorlage löschen",
|
||||
"templates.content": "Vorlageninhalt",
|
||||
"templates.field.kind": "Art",
|
||||
"templates.field.enabled": "Aktiviert",
|
||||
"templates.validate": "Validieren",
|
||||
"templates.validate.valid": "Vorlagensyntax ist gültig",
|
||||
"templates.validate.invalid": "Vorlagensyntaxfehler: {count}",
|
||||
"templates.validate.checking": "Wird validiert...",
|
||||
"templates.kind.post": "Beitrag",
|
||||
"templates.kind.list": "Liste",
|
||||
"templates.kind.not_found": "Nicht gefunden",
|
||||
"templates.kind.partial": "Partial",
|
||||
"sidebar.tagCloud": "Tag-Wolke",
|
||||
"sidebar.createEdit": "Erstellen & Bearbeiten",
|
||||
"sidebar.mergeTags": "Tags zusammenführen",
|
||||
@@ -497,6 +514,8 @@
|
||||
"editor.field.slug": "Slug",
|
||||
"editor.field.categories": "Kategorien",
|
||||
"editor.field.content": "Inhalt",
|
||||
"editor.field.template": "Vorlage",
|
||||
"editor.field.templateDefault": "Standard",
|
||||
"editor.placeholder.tags": "Tags hinzufügen...",
|
||||
"editor.placeholder.author": "Autorenname",
|
||||
"editor.placeholder.categories": "Kategorien hinzufügen...",
|
||||
@@ -587,6 +606,7 @@
|
||||
"tagsView.removeColor": "Farbe entfernen",
|
||||
"tagsView.edit.title": "Tag bearbeiten: {name}",
|
||||
"tagsView.edit.action": "Bearbeiten",
|
||||
"tagsView.edit.postTemplate": "Beitragsvorlage",
|
||||
"tagsView.deleteAction": "Löschen",
|
||||
"tagsView.merge.title": "Tags zusammenführen",
|
||||
"tagsView.merge.description": "Wähle oben mehrere Tags aus und führe sie zu einem einzigen zusammen. Alle Beiträge werden aktualisiert.",
|
||||
@@ -683,6 +703,10 @@
|
||||
"settings.content.categoryColumn": "Kategorie",
|
||||
"settings.content.titleColumn": "Titel",
|
||||
"settings.content.actionsColumn": "Aktionen",
|
||||
"settings.content.postTemplateColumn": "Beitragsvorlage",
|
||||
"settings.content.listTemplateColumn": "Listenvorlage",
|
||||
"settings.content.postTemplateAria": "{category} Beitragsvorlage",
|
||||
"settings.content.listTemplateAria": "{category} Listenvorlage",
|
||||
"settings.content.renderInListsAria": "{category} in Listen anzeigen",
|
||||
"settings.content.showTitlesAria": "{category} Titel anzeigen",
|
||||
"settings.content.categoryTitleAria": "{category} Anzeigename",
|
||||
@@ -718,6 +742,9 @@
|
||||
"settings.data.rebuildScriptsLabel": "Skriptdatenbank neu aufbauen",
|
||||
"settings.data.rebuildScriptsDescription": "Alle Python-Skripte neu scannen und den Skript-Metadatenindex neu aufbauen.",
|
||||
"settings.data.rebuildScriptsAction": "Skripte neu aufbauen",
|
||||
"settings.data.rebuildTemplatesLabel": "Vorlagen-Datenbank neu aufbauen",
|
||||
"settings.data.rebuildTemplatesDescription": "Alle Liquid-Vorlagen neu scannen und den Vorlagen-Metadaten-Index neu aufbauen.",
|
||||
"settings.data.rebuildTemplatesAction": "Vorlagen neu aufbauen",
|
||||
"settings.data.rebuildLinksLabel": "Beitragslinks neu aufbauen",
|
||||
"settings.data.rebuildLinksDescription": "Alle Beiträge neu scannen und den internen Linkgraphen zwischen Beiträgen neu aufbauen.",
|
||||
"settings.data.rebuildLinksAction": "Links neu aufbauen",
|
||||
@@ -746,6 +773,13 @@
|
||||
"sidebar.scripts.createFailed": "Skript konnte nicht erstellt werden",
|
||||
"sidebar.scripts.deleteScript": "Skript löschen",
|
||||
"sidebar.scripts.deleteFailed": "Skript konnte nicht gelöscht werden",
|
||||
"sidebar.templates.header": "VORLAGEN",
|
||||
"sidebar.templates.newTemplate": "Neue Vorlage",
|
||||
"sidebar.templates.none": "Noch keine Vorlagen",
|
||||
"sidebar.templates.createTemplate": "Vorlage erstellen",
|
||||
"sidebar.templates.createFailed": "Vorlage konnte nicht erstellt werden",
|
||||
"sidebar.templates.deleteTemplate": "Vorlage löschen",
|
||||
"sidebar.templates.deleteFailed": "Vorlage konnte nicht gelöscht werden",
|
||||
"sidebar.import.none": "Noch keine Importdefinitionen",
|
||||
"sidebar.import.createDefinition": "Eine Importdefinition erstellen",
|
||||
"sidebar.import.deleteDefinition": "Importdefinition löschen",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"activity.media": "Media",
|
||||
"activity.scripts": "Scripts",
|
||||
"activity.tags": "Tags",
|
||||
"activity.templates": "Templates",
|
||||
"activity.aiAssistant": "AI Assistant",
|
||||
"activity.import": "Import",
|
||||
"activity.sourceControl": "Source Control",
|
||||
@@ -179,6 +180,9 @@
|
||||
"settings.toast.rebuildScriptsLoading": "Rebuilding scripts database...",
|
||||
"settings.toast.rebuildScriptsSuccess": "Scripts database rebuilt",
|
||||
"settings.toast.rebuildScriptsFailed": "Failed to rebuild scripts database",
|
||||
"settings.toast.rebuildTemplatesLoading": "Rebuilding templates database...",
|
||||
"settings.toast.rebuildTemplatesSuccess": "Templates database rebuilt",
|
||||
"settings.toast.rebuildTemplatesFailed": "Failed to rebuild templates database",
|
||||
"settings.toast.rebuildLinksLoading": "Rebuilding post links...",
|
||||
"settings.toast.rebuildLinksSuccess": "Post links rebuilt",
|
||||
"settings.toast.rebuildLinksFailed": "Failed to rebuild post links",
|
||||
@@ -451,6 +455,19 @@
|
||||
"scripts.kind.utility": "utility",
|
||||
"scripts.kind.macro": "macro",
|
||||
"scripts.kind.transform": "transform",
|
||||
"templates.save": "Save Template",
|
||||
"templates.delete": "Delete Template",
|
||||
"templates.content": "Template Content",
|
||||
"templates.field.kind": "Kind",
|
||||
"templates.field.enabled": "Enabled",
|
||||
"templates.validate": "Validate",
|
||||
"templates.validate.valid": "Template syntax is valid",
|
||||
"templates.validate.invalid": "Template syntax errors: {count}",
|
||||
"templates.validate.checking": "Validating...",
|
||||
"templates.kind.post": "post",
|
||||
"templates.kind.list": "list",
|
||||
"templates.kind.not_found": "not found",
|
||||
"templates.kind.partial": "partial",
|
||||
"sidebar.tagCloud": "Tag Cloud",
|
||||
"sidebar.createEdit": "Create & Edit",
|
||||
"sidebar.mergeTags": "Merge Tags",
|
||||
@@ -497,6 +514,8 @@
|
||||
"editor.field.slug": "Slug",
|
||||
"editor.field.categories": "Categories",
|
||||
"editor.field.content": "Content",
|
||||
"editor.field.template": "Template",
|
||||
"editor.field.templateDefault": "Default",
|
||||
"editor.placeholder.tags": "Add tags...",
|
||||
"editor.placeholder.author": "Author name",
|
||||
"editor.placeholder.categories": "Add categories...",
|
||||
@@ -587,6 +606,7 @@
|
||||
"tagsView.removeColor": "Remove color",
|
||||
"tagsView.edit.title": "Edit Tag: {name}",
|
||||
"tagsView.edit.action": "Edit",
|
||||
"tagsView.edit.postTemplate": "Post Template",
|
||||
"tagsView.deleteAction": "Delete",
|
||||
"tagsView.merge.title": "Merge Tags",
|
||||
"tagsView.merge.description": "Select multiple tags above, then merge them into a single tag. All posts will be updated.",
|
||||
@@ -683,6 +703,10 @@
|
||||
"settings.content.categoryColumn": "Category",
|
||||
"settings.content.titleColumn": "Title",
|
||||
"settings.content.actionsColumn": "Actions",
|
||||
"settings.content.postTemplateColumn": "Post Template",
|
||||
"settings.content.listTemplateColumn": "List Template",
|
||||
"settings.content.postTemplateAria": "{category} post template",
|
||||
"settings.content.listTemplateAria": "{category} list template",
|
||||
"settings.content.renderInListsAria": "{category} render in lists",
|
||||
"settings.content.showTitlesAria": "{category} show titles",
|
||||
"settings.content.categoryTitleAria": "{category} display title",
|
||||
@@ -718,6 +742,9 @@
|
||||
"settings.data.rebuildScriptsLabel": "Rebuild Scripts Database",
|
||||
"settings.data.rebuildScriptsDescription": "Re-scan all Python scripts and rebuild the scripts metadata index.",
|
||||
"settings.data.rebuildScriptsAction": "Rebuild Scripts",
|
||||
"settings.data.rebuildTemplatesLabel": "Rebuild Templates Database",
|
||||
"settings.data.rebuildTemplatesDescription": "Re-scan all Liquid templates and rebuild the templates metadata index.",
|
||||
"settings.data.rebuildTemplatesAction": "Rebuild Templates",
|
||||
"settings.data.rebuildLinksLabel": "Rebuild Post Links",
|
||||
"settings.data.rebuildLinksDescription": "Re-scan all posts and rebuild the internal link graph between posts.",
|
||||
"settings.data.rebuildLinksAction": "Rebuild Links",
|
||||
@@ -746,6 +773,13 @@
|
||||
"sidebar.scripts.createFailed": "Failed to create script",
|
||||
"sidebar.scripts.deleteScript": "Delete script",
|
||||
"sidebar.scripts.deleteFailed": "Failed to delete script",
|
||||
"sidebar.templates.header": "TEMPLATES",
|
||||
"sidebar.templates.newTemplate": "New Template",
|
||||
"sidebar.templates.none": "No templates yet",
|
||||
"sidebar.templates.createTemplate": "Create a template",
|
||||
"sidebar.templates.createFailed": "Failed to create template",
|
||||
"sidebar.templates.deleteTemplate": "Delete template",
|
||||
"sidebar.templates.deleteFailed": "Failed to delete template",
|
||||
"sidebar.import.none": "No import definitions yet",
|
||||
"sidebar.import.createDefinition": "Create an import definition",
|
||||
"sidebar.import.deleteDefinition": "Delete import definition",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"activity.media": "Medios",
|
||||
"activity.scripts": "Scripts",
|
||||
"activity.tags": "Etiquetas",
|
||||
"activity.templates": "Plantillas",
|
||||
"activity.aiAssistant": "Asistente IA",
|
||||
"activity.import": "Importar",
|
||||
"activity.sourceControl": "Control de código fuente",
|
||||
@@ -179,6 +180,9 @@
|
||||
"settings.toast.rebuildScriptsLoading": "Reconstruyendo base de datos de scripts...",
|
||||
"settings.toast.rebuildScriptsSuccess": "Base de datos de scripts reconstruida",
|
||||
"settings.toast.rebuildScriptsFailed": "No se pudo reconstruir la base de datos de scripts",
|
||||
"settings.toast.rebuildTemplatesLoading": "Reconstruyendo la base de datos de plantillas...",
|
||||
"settings.toast.rebuildTemplatesSuccess": "Base de datos de plantillas reconstruida",
|
||||
"settings.toast.rebuildTemplatesFailed": "Error al reconstruir la base de datos de plantillas",
|
||||
"settings.toast.rebuildLinksLoading": "Reconstruyendo enlaces de entradas...",
|
||||
"settings.toast.rebuildLinksSuccess": "Enlaces de publicaciones reconstruidos",
|
||||
"settings.toast.rebuildLinksFailed": "No se pudieron reconstruir los enlaces de entradas",
|
||||
@@ -451,6 +455,19 @@
|
||||
"scripts.kind.utility": "utility",
|
||||
"scripts.kind.macro": "macro",
|
||||
"scripts.kind.transform": "transform",
|
||||
"templates.save": "Guardar plantilla",
|
||||
"templates.delete": "Eliminar plantilla",
|
||||
"templates.content": "Contenido de la plantilla",
|
||||
"templates.field.kind": "Tipo",
|
||||
"templates.field.enabled": "Habilitado",
|
||||
"templates.validate": "Validar",
|
||||
"templates.validate.valid": "La sintaxis de la plantilla es válida",
|
||||
"templates.validate.invalid": "Errores de sintaxis de la plantilla: {count}",
|
||||
"templates.validate.checking": "Validando...",
|
||||
"templates.kind.post": "entrada",
|
||||
"templates.kind.list": "lista",
|
||||
"templates.kind.not_found": "no encontrado",
|
||||
"templates.kind.partial": "parcial",
|
||||
"sidebar.tagCloud": "Nube de etiquetas",
|
||||
"sidebar.createEdit": "Crear y editar",
|
||||
"sidebar.mergeTags": "Combinar etiquetas",
|
||||
@@ -497,6 +514,8 @@
|
||||
"editor.field.slug": "Slug",
|
||||
"editor.field.categories": "Categorías",
|
||||
"editor.field.content": "Contenido",
|
||||
"editor.field.template": "Plantilla",
|
||||
"editor.field.templateDefault": "Predeterminada",
|
||||
"editor.placeholder.tags": "Agregar etiquetas...",
|
||||
"editor.placeholder.author": "Nombre del autor",
|
||||
"editor.placeholder.categories": "Agregar categorías...",
|
||||
@@ -587,6 +606,7 @@
|
||||
"tagsView.removeColor": "Quitar color",
|
||||
"tagsView.edit.title": "Editar etiqueta: {name}",
|
||||
"tagsView.edit.action": "Editar",
|
||||
"tagsView.edit.postTemplate": "Plantilla de entrada",
|
||||
"tagsView.deleteAction": "Eliminar",
|
||||
"tagsView.merge.title": "Combinar etiquetas",
|
||||
"tagsView.merge.description": "Selecciona varias etiquetas arriba y combínalas en una sola. Se actualizarán todas las entradas.",
|
||||
@@ -683,6 +703,10 @@
|
||||
"settings.content.categoryColumn": "Categoría",
|
||||
"settings.content.titleColumn": "Título",
|
||||
"settings.content.actionsColumn": "Acciones",
|
||||
"settings.content.postTemplateColumn": "Plantilla de entrada",
|
||||
"settings.content.listTemplateColumn": "Plantilla de lista",
|
||||
"settings.content.postTemplateAria": "{category} plantilla de entrada",
|
||||
"settings.content.listTemplateAria": "{category} plantilla de lista",
|
||||
"settings.content.renderInListsAria": "{category} mostrar en listas",
|
||||
"settings.content.showTitlesAria": "{category} mostrar títulos",
|
||||
"settings.content.categoryTitleAria": "Título visible para {category}",
|
||||
@@ -718,6 +742,9 @@
|
||||
"settings.data.rebuildScriptsLabel": "Reconstruir base de datos de scripts",
|
||||
"settings.data.rebuildScriptsDescription": "Reescanea todos los scripts de Python y reconstruye el índice de metadatos de scripts.",
|
||||
"settings.data.rebuildScriptsAction": "Reconstruir scripts",
|
||||
"settings.data.rebuildTemplatesLabel": "Reconstruir base de datos de plantillas",
|
||||
"settings.data.rebuildTemplatesDescription": "Re-escanear todas las plantillas Liquid y reconstruir el índice de metadatos.",
|
||||
"settings.data.rebuildTemplatesAction": "Reconstruir plantillas",
|
||||
"settings.data.rebuildLinksLabel": "Reconstruir enlaces de publicaciones",
|
||||
"settings.data.rebuildLinksDescription": "Reescanea todas las publicaciones y reconstruye el grafo interno de enlaces entre publicaciones.",
|
||||
"settings.data.rebuildLinksAction": "Reconstruir enlaces",
|
||||
@@ -746,6 +773,13 @@
|
||||
"sidebar.scripts.createFailed": "No se pudo crear el script",
|
||||
"sidebar.scripts.deleteScript": "Eliminar script",
|
||||
"sidebar.scripts.deleteFailed": "No se pudo eliminar el script",
|
||||
"sidebar.templates.header": "PLANTILLAS",
|
||||
"sidebar.templates.newTemplate": "Nueva plantilla",
|
||||
"sidebar.templates.none": "Aún no hay plantillas",
|
||||
"sidebar.templates.createTemplate": "Crear una plantilla",
|
||||
"sidebar.templates.createFailed": "No se pudo crear la plantilla",
|
||||
"sidebar.templates.deleteTemplate": "Eliminar plantilla",
|
||||
"sidebar.templates.deleteFailed": "No se pudo eliminar la plantilla",
|
||||
"sidebar.import.none": "Sin definiciones de importación",
|
||||
"sidebar.import.createDefinition": "Crear definición",
|
||||
"sidebar.import.deleteDefinition": "Eliminar definición",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"activity.media": "Médias",
|
||||
"activity.scripts": "Scripts",
|
||||
"activity.tags": "Étiquettes",
|
||||
"activity.templates": "Modèles",
|
||||
"activity.aiAssistant": "Assistant IA",
|
||||
"activity.import": "Importation",
|
||||
"activity.sourceControl": "Contrôle de source",
|
||||
@@ -177,6 +178,9 @@
|
||||
"settings.toast.rebuildScriptsLoading": "Reconstruction de la base des scripts...",
|
||||
"settings.toast.rebuildScriptsSuccess": "Base des scripts reconstruite",
|
||||
"settings.toast.rebuildScriptsFailed": "Impossible de reconstruire la base des scripts",
|
||||
"settings.toast.rebuildTemplatesLoading": "Reconstruction de la base de données des modèles...",
|
||||
"settings.toast.rebuildTemplatesSuccess": "Base de données des modèles reconstruite",
|
||||
"settings.toast.rebuildTemplatesFailed": "Échec de la reconstruction de la base de données des modèles",
|
||||
"settings.toast.rebuildLinksLoading": "Reconstruction des liens d’articles...",
|
||||
"settings.toast.rebuildLinksSuccess": "Liens d’articles reconstruits",
|
||||
"settings.toast.rebuildLinksFailed": "Impossible de reconstruire les liens d’articles",
|
||||
@@ -449,6 +453,19 @@
|
||||
"scripts.kind.utility": "utility",
|
||||
"scripts.kind.macro": "macro",
|
||||
"scripts.kind.transform": "transform",
|
||||
"templates.save": "Enregistrer le modèle",
|
||||
"templates.delete": "Supprimer le modèle",
|
||||
"templates.content": "Contenu du modèle",
|
||||
"templates.field.kind": "Type",
|
||||
"templates.field.enabled": "Activé",
|
||||
"templates.validate": "Valider",
|
||||
"templates.validate.valid": "La syntaxe du modèle est valide",
|
||||
"templates.validate.invalid": "Erreurs de syntaxe du modèle : {count}",
|
||||
"templates.validate.checking": "Validation en cours...",
|
||||
"templates.kind.post": "article",
|
||||
"templates.kind.list": "liste",
|
||||
"templates.kind.not_found": "non trouvé",
|
||||
"templates.kind.partial": "partiel",
|
||||
"sidebar.tagCloud": "Nuage d’étiquettes",
|
||||
"sidebar.createEdit": "Créer & modifier",
|
||||
"sidebar.mergeTags": "Fusionner les étiquettes",
|
||||
@@ -495,6 +512,8 @@
|
||||
"editor.field.slug": "Slug",
|
||||
"editor.field.categories": "Catégories",
|
||||
"editor.field.content": "Contenu",
|
||||
"editor.field.template": "Modèle",
|
||||
"editor.field.templateDefault": "Par défaut",
|
||||
"editor.placeholder.tags": "Ajouter des étiquettes...",
|
||||
"editor.placeholder.author": "Nom de l’auteur",
|
||||
"editor.placeholder.categories": "Ajouter des catégories...",
|
||||
@@ -585,6 +604,7 @@
|
||||
"tagsView.removeColor": "Supprimer la couleur",
|
||||
"tagsView.edit.title": "Modifier le tag : {name}",
|
||||
"tagsView.edit.action": "Modifier",
|
||||
"tagsView.edit.postTemplate": "Modèle d'article",
|
||||
"tagsView.deleteAction": "Supprimer",
|
||||
"tagsView.merge.title": "Fusionner des tags",
|
||||
"tagsView.merge.description": "Sélectionnez plusieurs tags ci-dessus puis fusionnez-les en un seul. Tous les articles seront mis à jour.",
|
||||
@@ -681,6 +701,10 @@
|
||||
"settings.content.categoryColumn": "Catégorie",
|
||||
"settings.content.titleColumn": "Titre",
|
||||
"settings.content.actionsColumn": "Actions",
|
||||
"settings.content.postTemplateColumn": "Modèle d'article",
|
||||
"settings.content.listTemplateColumn": "Modèle de liste",
|
||||
"settings.content.postTemplateAria": "{category} modèle d'article",
|
||||
"settings.content.listTemplateAria": "{category} modèle de liste",
|
||||
"settings.content.renderInListsAria": "{category} afficher dans les listes",
|
||||
"settings.content.showTitlesAria": "{category} afficher les titres",
|
||||
"settings.content.categoryTitleAria": "Titre affiché pour {category}",
|
||||
@@ -716,6 +740,9 @@
|
||||
"settings.data.rebuildScriptsLabel": "Reconstruire la base des scripts",
|
||||
"settings.data.rebuildScriptsDescription": "Réanalyse tous les scripts Python et reconstruit l’index des métadonnées de scripts.",
|
||||
"settings.data.rebuildScriptsAction": "Reconstruire les scripts",
|
||||
"settings.data.rebuildTemplatesLabel": "Reconstruire la base de données des modèles",
|
||||
"settings.data.rebuildTemplatesDescription": "Re-scanner tous les modèles Liquid et reconstruire l'index des métadonnées.",
|
||||
"settings.data.rebuildTemplatesAction": "Reconstruire les modèles",
|
||||
"settings.data.rebuildLinksLabel": "Reconstruire les liens d’articles",
|
||||
"settings.data.rebuildLinksDescription": "Réanalyse tous les articles et reconstruit le graphe interne des liens entre articles.",
|
||||
"settings.data.rebuildLinksAction": "Reconstruire les liens",
|
||||
@@ -744,6 +771,13 @@
|
||||
"sidebar.scripts.createFailed": "Impossible de créer le script",
|
||||
"sidebar.scripts.deleteScript": "Supprimer le script",
|
||||
"sidebar.scripts.deleteFailed": "Impossible de supprimer le script",
|
||||
"sidebar.templates.header": "MODÈLES",
|
||||
"sidebar.templates.newTemplate": "Nouveau modèle",
|
||||
"sidebar.templates.none": "Aucun modèle",
|
||||
"sidebar.templates.createTemplate": "Créer un modèle",
|
||||
"sidebar.templates.createFailed": "Impossible de créer le modèle",
|
||||
"sidebar.templates.deleteTemplate": "Supprimer le modèle",
|
||||
"sidebar.templates.deleteFailed": "Impossible de supprimer le modèle",
|
||||
"sidebar.import.none": "Aucune définition d’import",
|
||||
"sidebar.import.createDefinition": "Créer une définition",
|
||||
"sidebar.import.deleteDefinition": "Supprimer la définition",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"activity.media": "Contenuti media",
|
||||
"activity.scripts": "Script",
|
||||
"activity.tags": "Tag",
|
||||
"activity.templates": "Modelli",
|
||||
"activity.aiAssistant": "Assistente IA",
|
||||
"activity.import": "Importa",
|
||||
"activity.sourceControl": "Controllo sorgente",
|
||||
@@ -177,6 +178,9 @@
|
||||
"settings.toast.rebuildScriptsLoading": "Ricostruzione database script...",
|
||||
"settings.toast.rebuildScriptsSuccess": "Database script ricostruito",
|
||||
"settings.toast.rebuildScriptsFailed": "Impossibile ricostruire il database degli script",
|
||||
"settings.toast.rebuildTemplatesLoading": "Ricostruzione del database dei modelli...",
|
||||
"settings.toast.rebuildTemplatesSuccess": "Database dei modelli ricostruito",
|
||||
"settings.toast.rebuildTemplatesFailed": "Impossibile ricostruire il database dei modelli",
|
||||
"settings.toast.rebuildLinksLoading": "Ricostruzione dei link dei post...",
|
||||
"settings.toast.rebuildLinksSuccess": "Link dei post ricostruiti",
|
||||
"settings.toast.rebuildLinksFailed": "Impossibile ricostruire i link dei post",
|
||||
@@ -449,6 +453,19 @@
|
||||
"scripts.kind.utility": "utility",
|
||||
"scripts.kind.macro": "macro",
|
||||
"scripts.kind.transform": "transform",
|
||||
"templates.save": "Salva modello",
|
||||
"templates.delete": "Elimina modello",
|
||||
"templates.content": "Contenuto del modello",
|
||||
"templates.field.kind": "Tipo",
|
||||
"templates.field.enabled": "Abilitato",
|
||||
"templates.validate": "Valida",
|
||||
"templates.validate.valid": "La sintassi del modello è valida",
|
||||
"templates.validate.invalid": "Errori di sintassi del modello: {count}",
|
||||
"templates.validate.checking": "Validazione in corso...",
|
||||
"templates.kind.post": "articolo",
|
||||
"templates.kind.list": "elenco",
|
||||
"templates.kind.not_found": "non trovato",
|
||||
"templates.kind.partial": "parziale",
|
||||
"sidebar.tagCloud": "Nuvola tag",
|
||||
"sidebar.createEdit": "Crea e modifica",
|
||||
"sidebar.mergeTags": "Unisci tag",
|
||||
@@ -495,6 +512,8 @@
|
||||
"editor.field.slug": "Slug",
|
||||
"editor.field.categories": "Categorie",
|
||||
"editor.field.content": "Contenuto",
|
||||
"editor.field.template": "Modello",
|
||||
"editor.field.templateDefault": "Predefinito",
|
||||
"editor.placeholder.tags": "Aggiungi tag...",
|
||||
"editor.placeholder.author": "Nome autore",
|
||||
"editor.placeholder.categories": "Aggiungi categorie...",
|
||||
@@ -585,6 +604,7 @@
|
||||
"tagsView.removeColor": "Rimuovi colore",
|
||||
"tagsView.edit.title": "Modifica tag: {name}",
|
||||
"tagsView.edit.action": "Modifica",
|
||||
"tagsView.edit.postTemplate": "Modello articolo",
|
||||
"tagsView.deleteAction": "Elimina",
|
||||
"tagsView.merge.title": "Unisci tag",
|
||||
"tagsView.merge.description": "Seleziona più tag sopra, quindi uniscili in un unico tag. Tutti i post verranno aggiornati.",
|
||||
@@ -681,6 +701,10 @@
|
||||
"settings.content.categoryColumn": "Categoria",
|
||||
"settings.content.titleColumn": "Titolo",
|
||||
"settings.content.actionsColumn": "Azioni",
|
||||
"settings.content.postTemplateColumn": "Modello articolo",
|
||||
"settings.content.listTemplateColumn": "Modello elenco",
|
||||
"settings.content.postTemplateAria": "{category} modello articolo",
|
||||
"settings.content.listTemplateAria": "{category} modello elenco",
|
||||
"settings.content.renderInListsAria": "{category} mostra negli elenchi",
|
||||
"settings.content.showTitlesAria": "{category} mostra i titoli",
|
||||
"settings.content.categoryTitleAria": "Titolo visualizzato per {category}",
|
||||
@@ -716,6 +740,9 @@
|
||||
"settings.data.rebuildScriptsLabel": "Ricostruisci database script",
|
||||
"settings.data.rebuildScriptsDescription": "Rianalizza tutti gli script Python e ricostruisce l’indice dei metadati degli script.",
|
||||
"settings.data.rebuildScriptsAction": "Ricostruisci script",
|
||||
"settings.data.rebuildTemplatesLabel": "Ricostruisci database modelli",
|
||||
"settings.data.rebuildTemplatesDescription": "Scansiona tutti i modelli Liquid e ricostruisci l'indice dei metadati.",
|
||||
"settings.data.rebuildTemplatesAction": "Ricostruisci modelli",
|
||||
"settings.data.rebuildLinksLabel": "Ricostruisci collegamenti post",
|
||||
"settings.data.rebuildLinksDescription": "Rianalizza tutti i post e ricostruisce il grafo interno dei collegamenti tra post.",
|
||||
"settings.data.rebuildLinksAction": "Ricostruisci collegamenti",
|
||||
@@ -744,6 +771,13 @@
|
||||
"sidebar.scripts.createFailed": "Impossibile creare lo script",
|
||||
"sidebar.scripts.deleteScript": "Elimina script",
|
||||
"sidebar.scripts.deleteFailed": "Impossibile eliminare lo script",
|
||||
"sidebar.templates.header": "MODELLI",
|
||||
"sidebar.templates.newTemplate": "Nuovo modello",
|
||||
"sidebar.templates.none": "Nessun modello",
|
||||
"sidebar.templates.createTemplate": "Crea un modello",
|
||||
"sidebar.templates.createFailed": "Impossibile creare il modello",
|
||||
"sidebar.templates.deleteTemplate": "Elimina modello",
|
||||
"sidebar.templates.deleteFailed": "Impossibile eliminare il modello",
|
||||
"sidebar.import.none": "Nessuna definizione di importazione",
|
||||
"sidebar.import.createDefinition": "Crea definizione",
|
||||
"sidebar.import.deleteDefinition": "Elimina definizione",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Tab } from '../store/appStore';
|
||||
import type { SidebarView } from './sidebarViewRegistry';
|
||||
|
||||
export type ActivityId = 'posts' | 'pages' | 'media' | 'scripts' | 'tags' | 'chat' | 'import' | 'git' | 'settings';
|
||||
export type ActivityId = 'posts' | 'pages' | 'media' | 'scripts' | 'templates' | 'tags' | 'chat' | 'import' | 'git' | 'settings';
|
||||
|
||||
export interface ActivitySnapshot {
|
||||
activeView: SidebarView;
|
||||
@@ -50,6 +50,13 @@ const ACTIVITY_CONFIG: Record<ActivityId, ActivityConfig> = {
|
||||
activeStrategy: 'sidebar-owner',
|
||||
clickStrategy: 'sidebar-toggle',
|
||||
},
|
||||
templates: {
|
||||
id: 'templates',
|
||||
view: 'templates',
|
||||
labelKey: 'activity.templates',
|
||||
activeStrategy: 'sidebar-owner',
|
||||
clickStrategy: 'sidebar-toggle',
|
||||
},
|
||||
tags: {
|
||||
id: 'tags',
|
||||
view: 'tags',
|
||||
|
||||
@@ -16,7 +16,8 @@ export type EditorRoute =
|
||||
| 'documentation'
|
||||
| 'api-documentation'
|
||||
| 'site-validation'
|
||||
| 'scripts';
|
||||
| 'scripts'
|
||||
| 'templates';
|
||||
|
||||
export const EDITOR_TAB_ROUTE_REGISTRY: Record<TabType, Exclude<EditorRoute, 'dashboard'>> = {
|
||||
post: 'post',
|
||||
@@ -33,6 +34,7 @@ export const EDITOR_TAB_ROUTE_REGISTRY: Record<TabType, Exclude<EditorRoute, 'da
|
||||
'api-documentation': 'api-documentation',
|
||||
'site-validation': 'site-validation',
|
||||
scripts: 'scripts',
|
||||
templates: 'templates',
|
||||
};
|
||||
|
||||
export interface EditorRouteResolution {
|
||||
|
||||
@@ -3,6 +3,7 @@ export const SIDEBAR_VIEW_REGISTRY = [
|
||||
'pages',
|
||||
'media',
|
||||
'scripts',
|
||||
'templates',
|
||||
'settings',
|
||||
'tags',
|
||||
'chat',
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface CanonicalTabSpec {
|
||||
export type EntityTabType = 'post' | 'media';
|
||||
export type EntityTabOpenIntent = 'preview' | 'pin';
|
||||
export type ScriptTabOpenIntent = 'preview' | 'pin';
|
||||
export type TemplateTabOpenIntent = 'preview' | 'pin';
|
||||
export type GitDiffResourceOpenIntent = 'preview' | 'pin';
|
||||
|
||||
const SINGLETON_TOOL_TAB_REGISTRY: Record<SingletonToolTabKey, CanonicalTabSpec> = {
|
||||
@@ -112,6 +113,22 @@ export function openScriptTab(
|
||||
openTab(getScriptTabSpec(scriptId, intent));
|
||||
}
|
||||
|
||||
export function getTemplateTabSpec(templateId: string, intent: TemplateTabOpenIntent): CanonicalTabSpec {
|
||||
return {
|
||||
type: 'templates',
|
||||
id: templateId,
|
||||
isTransient: intent === 'preview',
|
||||
};
|
||||
}
|
||||
|
||||
export function openTemplateTab(
|
||||
openTab: (tab: CanonicalTabSpec) => void,
|
||||
templateId: string,
|
||||
intent: TemplateTabOpenIntent,
|
||||
): void {
|
||||
openTab(getTemplateTabSpec(templateId, intent));
|
||||
}
|
||||
|
||||
export function getGitDiffFileTabId(filePath: string): string {
|
||||
return `git-diff:${filePath}`;
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import type {
|
||||
const STORAGE_KEY = 'bds-app-state';
|
||||
|
||||
// Tab types
|
||||
export type TabType = 'post' | 'media' | 'settings' | 'style' | 'tags' | 'chat' | 'import' | 'menu-editor' | 'metadata-diff' | 'git-diff' | 'documentation' | 'api-documentation' | 'site-validation' | 'scripts';
|
||||
export type TabType = 'post' | 'media' | 'settings' | 'style' | 'tags' | 'chat' | 'import' | 'menu-editor' | 'metadata-diff' | 'git-diff' | 'documentation' | 'api-documentation' | 'site-validation' | 'scripts' | 'templates';
|
||||
|
||||
export interface Tab {
|
||||
type: TabType;
|
||||
|
||||
@@ -4,4 +4,4 @@ export { unescapeMacroSyntax } from './markdownEscape';
|
||||
export { groupPostsByStatus, type GroupedPosts, type PostStatus } from './postGrouping';
|
||||
export { loadTabsForProject, saveTabsForProject } from './tabPersistence';
|
||||
export { buildTagColorMap, loadTagColorMap } from './tagColors';
|
||||
export { BDS_EVENT_SCRIPTS_CHANGED, addWindowEventListener, dispatchWindowEvent, type BdsWindowEventName } from './windowEvents';
|
||||
export { BDS_EVENT_SCRIPTS_CHANGED, BDS_EVENT_TEMPLATES_CHANGED, addWindowEventListener, dispatchWindowEvent, type BdsWindowEventName } from './windowEvents';
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
export const BDS_EVENT_SCRIPTS_CHANGED = 'bds:scripts-changed' as const;
|
||||
export const BDS_EVENT_TEMPLATES_CHANGED = 'bds:templates-changed' as const;
|
||||
|
||||
export type BdsWindowEventName =
|
||||
| typeof BDS_EVENT_SCRIPTS_CHANGED
|
||||
| typeof BDS_EVENT_TEMPLATES_CHANGED
|
||||
| 'bds:site-validation-updated';
|
||||
|
||||
export function addWindowEventListener<TDetail = unknown>(
|
||||
|
||||
Reference in New Issue
Block a user