fix(frontend): 修复纸页刷新宽度并完善侧栏和图表主题

This commit is contained in:
2026-09-05 19:23:21 +08:00
parent a63f6c57e0
commit 8692910508
10 changed files with 165 additions and 35 deletions
@@ -62,6 +62,22 @@ const fontSizeInput = ref(16)
let crepe: Crepe | null = null
let disposeLanguagePicker: (() => void) | undefined
let disposeCodeLabels: (() => void) | undefined
const diagramPreviews = new Map<string, { source: string; apply: (value: HTMLElement) => void }>()
function renderDiagram(source: string, apply: (value: HTMLElement) => void) {
for (const [id, entry] of diagramPreviews) {
if (entry.apply === apply) diagramPreviews.delete(id)
}
const element = createMermaidPreview(source, themeStore.isDark, apply)
diagramPreviews.set(element.id, { source, apply })
return element
}
watch(() => themeStore.currentThemeId, () => {
const current = [...diagramPreviews.entries()]
diagramPreviews.clear()
for (const [id, entry] of current) {
if (editorRoot.value?.querySelector(`[id="${id}"]`)) entry.apply(renderDiagram(entry.source, entry.apply))
}
}, { flush: 'post' })
function applyProofingPreferences() {
const editable = editorRoot.value?.querySelector<HTMLElement>('.ProseMirror')
@@ -206,7 +222,7 @@ onMounted(async () => {
languages: shikiLanguages(themeStore.resolvedCodeBlockTheme),
renderLanguage: renderCodeLanguage,
renderPreview: (language, content, applyPreview) => language.trim().toLowerCase() === 'mermaid'
? createMermaidPreview(content, themeStore.isDark, applyPreview)
? renderDiagram(content, applyPreview)
: config.renderPreview(language, content, applyPreview),
extensions: [basicSetup, keymap.of([indentWithTab]), shikiEditorTheme(themeStore.resolvedCodeBlockTheme)],
})))
@@ -242,7 +258,7 @@ watch(() => editorStore.headingRequest, request => {
})
})
onBeforeUnmount(() => { disposeCodeLabels?.(); disposeLanguagePicker?.(); void crepe?.destroy() })
onBeforeUnmount(() => { diagramPreviews.clear(); disposeCodeLabels?.(); disposeLanguagePicker?.(); void crepe?.destroy() })
defineExpose({ getEditor: () => crepe?.editor })
</script>
@@ -48,6 +48,16 @@ afterEach(() => {
})
describe('FileTreePanel file switching', () => {
it('expands every nested folder from the toolbar', async () => {
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/workspace', component: { template: '<div />' } }] })
await router.push('/workspace')
const store = useWorkspaceStore()
store.fileTree = [{ id: 'a', name: 'A', path: '/a', type: 'folder', is_open: false, children: [{ id: 'b', name: 'B', path: '/a/b', type: 'folder', is_open: false }] }]
wrapper = mount(FileTreePanel, { global: { plugins: [router] } })
await wrapper.get('[aria-label="全部展开文件夹"]').trigger('click')
expect(store.fileTree[0]!.is_open).toBe(true)
expect(store.fileTree[0]!.children![0]!.is_open).toBe(true)
})
it('switches full-height panels using tabs and preserves the file search', async () => {
const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/workspace', component: { template: '<div />' } }] })
await router.push('/workspace')
@@ -68,6 +68,12 @@ const filteredTree = computed(() => {
})
return filter(workspaceStore.fileTree)
})
function expandAllFiles() {
const expand = (nodes: FileNode[]) => nodes.forEach(node => {
if (node.type === 'folder') { node.is_open = true; expand(node.children ?? []) }
})
expand(workspaceStore.fileTree)
}
let lastScrollTop = 0
function revealSearch(event: WheelEvent) {
if (activeTab.value !== 'files') return
@@ -216,6 +222,7 @@ function containingFolder(path: string): string {
<button type="button" :title="t('新建笔记', 'New note')" :aria-label="t('新建笔记', 'New note')" @click.stop="beginCreate('file', selectedFolderPath)"><AppIcon :icon="DocumentAdd" /></button>
<button type="button" :title="t('新建文件夹', 'New folder')" :aria-label="t('新建文件夹', 'New folder')" @click.stop="beginCreate('folder', selectedFolderPath)"><AppIcon :icon="FolderAdd" /></button>
<button type="button" :aria-label="t('搜索文件', 'Search files')" :aria-expanded="searchVisible" @click="searchVisible = !searchVisible">{{ t('搜索', 'Search') }}</button>
<button type="button" :aria-label="t('全部展开文件夹', 'Expand all folders')" @click="expandAllFiles">{{ t('全部展开', 'Expand all') }}</button>
</div>
<div v-if="searchVisible || searchQuery || searchFocused" class="file-search">
<input v-model="searchQuery" type="search" :placeholder="t('搜索文件或文件夹…', 'Search files or folders…')" :aria-label="t('搜索文件或文件夹', 'Search files or folders')" @focus="searchFocused = true" @blur="searchFocused = false" />