From a1a4cb50d0a87fda78dbd9484edcf6a18a8ca4d5 Mon Sep 17 00:00:00 2001 From: lucaronin Date: Thu, 30 Apr 2026 04:39:49 +0200 Subject: [PATCH] feat: add sidebar navigation arrows --- docs/ARCHITECTURE.md | 2 +- lara.lock | 2 + src/App.tsx | 2 +- src/components/Sidebar.tsx | 13 ++- .../sidebar/SidebarNavArrows.test.tsx | 87 +++++++++++++++++++ src/components/sidebar/SidebarNavArrows.tsx | 73 ++++++++++++++++ src/components/sidebar/SidebarSections.tsx | 66 ++++++++++---- .../sidebar/SidebarTitleBar.test.tsx | 51 +++++++++++ src/lib/locales/de-DE.json | 2 + src/lib/locales/en.json | 2 + src/lib/locales/es-419.json | 2 + src/lib/locales/es-ES.json | 2 + src/lib/locales/fr-FR.json | 2 + src/lib/locales/it-IT.json | 2 + src/lib/locales/ja-JP.json | 2 + src/lib/locales/ko-KR.json | 2 + src/lib/locales/pt-BR.json | 2 + src/lib/locales/pt-PT.json | 2 + src/lib/locales/ru-RU.json | 2 + src/lib/locales/zh-CN.json | 2 + src/lib/locales/zh-TW.json | 2 + src/lib/productAnalytics.ts | 5 ++ 22 files changed, 306 insertions(+), 21 deletions(-) create mode 100644 src/components/sidebar/SidebarNavArrows.test.tsx create mode 100644 src/components/sidebar/SidebarNavArrows.tsx create mode 100644 src/components/sidebar/SidebarTitleBar.test.tsx diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 440132f2..74fb530a 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -203,7 +203,7 @@ flowchart TD └──────────────────────────────────────────────────────────────┘ ``` -- **Sidebar** (150-400px, resizable): Top-level filters (All Notes, Changes, Pulse), saved Views, collapsible type-based section groups, and a dedicated folder tree. The folder tree starts with a vault-root row labeled from the opened vault path, shows root-level files when selected, and nests user-created folders plus default vault folders such as `attachments/` and `views/` underneath it; only the dedicated `type/` directory stays hidden because note types already have their own sidebar section. Saved Views persist a top-level YAML `order` field in each view file and use the same ordered-list mental model as Types: pointer users can drag the existing view row, while keyboard users can use command-palette move actions. The folder tree supports inline folder creation and rename, exposes a right-click menu for rename/delete plus filesystem reveal/copy-path actions on mutable folders, and auto-expands ancestor folders when the current selection or rename target is nested. Type sections and folder rows also act as note drop targets: dropping a note on a type updates its `type:` frontmatter, while dropping it on a folder runs the same crash-safe move path as the command palette flow. Each type can have a custom icon, color, sort, and visibility set via its `type: Type` document; new type documents created by Tolaria are written at the vault root. +- **Sidebar** (150-400px, resizable): Top-level filters (All Notes, Changes, Pulse), saved Views, collapsible type-based section groups, and a dedicated folder tree. The title bar exposes Back/Forward buttons wired to the same note navigation history as the command palette, menu accelerators, and mouse navigation buttons. The folder tree starts with a vault-root row labeled from the opened vault path, shows root-level files when selected, and nests user-created folders plus default vault folders such as `attachments/` and `views/` underneath it; only the dedicated `type/` directory stays hidden because note types already have their own sidebar section. Saved Views persist a top-level YAML `order` field in each view file and use the same ordered-list mental model as Types: pointer users can drag the existing view row, while keyboard users can use command-palette move actions. The folder tree supports inline folder creation and rename, exposes a right-click menu for rename/delete plus filesystem reveal/copy-path actions on mutable folders, and auto-expands ancestor folders when the current selection or rename target is nested. Type sections and folder rows also act as note drop targets: dropping a note on a type updates its `type:` frontmatter, while dropping it on a folder runs the same crash-safe move path as the command palette flow. Each type can have a custom icon, color, sort, and visibility set via its `type: Type` document; new type documents created by Tolaria are written at the vault root. - **Note List / Pulse View** (200-500px, resizable): When a section group, filter, or saved view is selected, shows filtered notes with snippets, modified dates, status indicators, and per-context note-list controls. When `selection.kind === 'entity'`, the same pane enters **Neighborhood** mode: the source note is pinned at the top as a normal active row, outgoing relationship groups render first, inverse/backlink groups follow, empty groups stay visible with `0`, and duplicates across groups are allowed when multiple relationships are true. Plain click / `Enter` open the focused note without replacing the current Neighborhood, while Cmd/Ctrl-click and Cmd/Ctrl-`Enter` pivot the pane into the clicked note's Neighborhood. Folder-backed lists also show non-Markdown files: previewable image and PDF binaries get file indicators and open in the editor pane, while unsupported binaries remain muted instead of auto-launching an external app. Saved views reuse the same sort and visible-column controls as the built-in lists, and those changes persist back into the view `.yml` definition (`sort`, `listPropertiesDisplay`). When Pulse filter is active, shows `PulseView` — a chronological git activity feed grouped by day. - **Editor** (flex, fills remaining space): Single note open at a time (no tabs — see ADR-0003). Breadcrumb bar with word count and rich-editor width toggle, BlockNote rich text editor with wikilink support, Markdown-compatible inline/display math rendering, first-class Mermaid diagram blocks, markdown-safe formatting controls, and schema-backed fenced code block highlighting via `@blocknote/code-block`. Can toggle to diff view (modified files), raw CodeMirror view, or a wide rich-editor reading surface with preserved side margins; raw CodeMirror remains full-width and unaffected by note width mode. Binary image and PDF files render through `FilePreview` as ordinary vault files using Tauri asset URLs; external-open actions call `open_vault_file_external` so the target is validated against the active vault before the native default app opens it. Unsupported/broken binaries show explicit fallback states and keyboard focus returns to the note list on `Escape`. Decomposed into `Editor` (orchestrator), `EditorContent`, `FilePreview`, `EditorRightPanel`, `SingleEditorView`, with hooks `useDiffMode`, `useEditorFocus`, and `useEditorSave`, plus the `useRawMode`/`RawEditorView` pair for markdown source editing. Rich BlockNote input and raw CodeMirror input both route typed `->`, `<-`, and `<->` through the shared `src/utils/arrowLigatures.ts` resolver so arrow ligatures stay consistent across mode switches while escaped ASCII sequences remain literal. Navigation history (Cmd+[/]) replaces tabs. - **Inspector / AI Agent** (200-500px or 40px collapsed): Toggles between Inspector (frontmatter, relationships, instances, backlinks, git history) and AI Agent panel (the selected CLI agent with tool execution). The Sparkle icon in the breadcrumb bar toggles between them. Per-note `icon` is a suggested Inspector property and the command palette's "Set Note Icon" action opens that field directly. When viewing a Type note, the Inspector shows an **Instances** section listing all notes of that type (sorted by modified_at desc, capped at 50). diff --git a/lara.lock b/lara.lock index 04ff973e..8d758600 100644 --- a/lara.lock +++ b/lara.lock @@ -247,6 +247,8 @@ files: sidebar.nav.inbox: 3882d32c66e7e768145ecd8f104b0c08 sidebar.nav.allNotes: cd76cf851b08a9d2feafaf255bc1f4d5 sidebar.nav.archive: e727b00944f81e1d0a95c12886ac4641 + sidebar.nav.back: 0557fa923dcee4d0f86b1409f5c2167f + sidebar.nav.forward: 67d2f6740a8eaebf4d5c6f79be8da481 sidebar.group.favorites: 953bf4d5e8a1807c15dec4e255384b1d sidebar.group.views: 8f56a72923a6ca8ff53462533d89e567 sidebar.group.types: 6b20155008db90cf7589c07baa7334e9 diff --git a/src/App.tsx b/src/App.tsx index 6f67853a..a42a5e0d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1632,7 +1632,7 @@ function App() { {sidebarVisible && ( <>
- +
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 6f7af629..fb3f40b5 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -65,6 +65,10 @@ interface SidebarProps { locale?: AppLocale onCollapse?: () => void loading?: boolean + canGoBack?: boolean + canGoForward?: boolean + onGoBack?: () => void + onGoForward?: () => void } interface SidebarNavigationProps extends Pick< @@ -633,7 +637,14 @@ export const Sidebar = memo(function Sidebar(props: SidebarProps) { return ( diff --git a/src/components/sidebar/SidebarNavArrows.test.tsx b/src/components/sidebar/SidebarNavArrows.test.tsx new file mode 100644 index 00000000..b54d28e0 --- /dev/null +++ b/src/components/sidebar/SidebarNavArrows.test.tsx @@ -0,0 +1,87 @@ +import type { ComponentProps } from 'react' +import { render, screen, fireEvent } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { TooltipProvider } from '@/components/ui/tooltip' +import { SidebarNavArrows } from './SidebarNavArrows' + +const { trackNavigationHistoryButtonClickedMock } = vi.hoisted(() => ({ + trackNavigationHistoryButtonClickedMock: vi.fn(), +})) + +vi.mock('../../lib/productAnalytics', () => ({ + trackNavigationHistoryButtonClicked: trackNavigationHistoryButtonClickedMock, +})) + +function renderArrows(props: Partial> = {}) { + const defaults = { + canGoBack: true, + canGoForward: true, + onGoBack: vi.fn(), + onGoForward: vi.fn(), + } + const merged = { ...defaults, ...props } + return { + ...render( + + + , + ), + props: merged, + } +} + +describe('SidebarNavArrows', () => { + beforeEach(() => { + trackNavigationHistoryButtonClickedMock.mockClear() + }) + + it('renders back and forward buttons with default English labels', () => { + renderArrows() + expect(screen.getByRole('button', { name: 'Back' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Forward' })).toBeInTheDocument() + }) + + it('localizes button labels', () => { + renderArrows({ locale: 'zh-CN' }) + expect(screen.getByRole('button', { name: '返回' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: '前进' })).toBeInTheDocument() + }) + + it('calls and tracks back navigation when back is clicked', () => { + const { props } = renderArrows() + fireEvent.click(screen.getByRole('button', { name: 'Back' })) + expect(props.onGoBack).toHaveBeenCalledOnce() + expect(trackNavigationHistoryButtonClickedMock).toHaveBeenCalledWith('back') + }) + + it('calls and tracks forward navigation when forward is clicked', () => { + const { props } = renderArrows() + fireEvent.click(screen.getByRole('button', { name: 'Forward' })) + expect(props.onGoForward).toHaveBeenCalledOnce() + expect(trackNavigationHistoryButtonClickedMock).toHaveBeenCalledWith('forward') + }) + + it('disables back navigation when history is unavailable', () => { + const { props } = renderArrows({ canGoBack: false }) + const back = screen.getByRole('button', { name: 'Back' }) + expect(back).toBeDisabled() + fireEvent.click(back) + expect(props.onGoBack).not.toHaveBeenCalled() + expect(trackNavigationHistoryButtonClickedMock).not.toHaveBeenCalled() + }) + + it('disables forward navigation when future history is unavailable', () => { + const { props } = renderArrows({ canGoForward: false }) + const forward = screen.getByRole('button', { name: 'Forward' }) + expect(forward).toBeDisabled() + fireEvent.click(forward) + expect(props.onGoForward).not.toHaveBeenCalled() + expect(trackNavigationHistoryButtonClickedMock).not.toHaveBeenCalled() + }) + + it('marks both buttons as non-drag controls', () => { + renderArrows() + expect(screen.getByRole('button', { name: 'Back' })).toHaveAttribute('data-no-drag') + expect(screen.getByRole('button', { name: 'Forward' })).toHaveAttribute('data-no-drag') + }) +}) diff --git a/src/components/sidebar/SidebarNavArrows.tsx b/src/components/sidebar/SidebarNavArrows.tsx new file mode 100644 index 00000000..8656e8f2 --- /dev/null +++ b/src/components/sidebar/SidebarNavArrows.tsx @@ -0,0 +1,73 @@ +import { CaretLeft, CaretRight } from '@phosphor-icons/react' +import { Button } from '@/components/ui/button' +import { ActionTooltip } from '@/components/ui/action-tooltip' +import { APP_COMMAND_IDS, getAppCommandShortcutDisplay } from '../../hooks/appCommandCatalog' +import { translate, type AppLocale } from '../../lib/i18n' +import { trackNavigationHistoryButtonClicked } from '../../lib/productAnalytics' + +export interface SidebarNavArrowsProps { + canGoBack: boolean + canGoForward: boolean + onGoBack: () => void + onGoForward: () => void + locale?: AppLocale +} + +const navButtonClass = 'h-6 w-6 rounded text-muted-foreground hover:text-foreground disabled:opacity-40' + +export function SidebarNavArrows({ + canGoBack, + canGoForward, + onGoBack, + onGoForward, + locale = 'en', +}: SidebarNavArrowsProps) { + const backLabel = translate(locale, 'sidebar.nav.back') + const forwardLabel = translate(locale, 'sidebar.nav.forward') + const backShortcut = getAppCommandShortcutDisplay(APP_COMMAND_IDS.viewGoBack) + const forwardShortcut = getAppCommandShortcutDisplay(APP_COMMAND_IDS.viewGoForward) + + const handleBackClick = () => { + trackNavigationHistoryButtonClicked('back') + onGoBack() + } + const handleForwardClick = () => { + trackNavigationHistoryButtonClicked('forward') + onGoForward() + } + + return ( +
+ + + + + + +
+ ) +} diff --git a/src/components/sidebar/SidebarSections.tsx b/src/components/sidebar/SidebarSections.tsx index bb8978c5..14cf4663 100644 --- a/src/components/sidebar/SidebarSections.tsx +++ b/src/components/sidebar/SidebarSections.tsx @@ -14,6 +14,7 @@ import { CaretLeft, Plus, } from '@phosphor-icons/react' import { Button } from '@/components/ui/button' +import { TooltipProvider } from '@/components/ui/tooltip' import { type SectionGroup, isSelectionActive, SectionContent, VisibilityPopover, } from '../SidebarParts' @@ -21,6 +22,7 @@ import { TypeCustomizePopover } from '../TypeCustomizePopover' import { useDragRegion } from '../../hooks/useDragRegion' import { SidebarGroupHeader } from './SidebarGroupHeader' import { SidebarViewItem } from './SidebarViewItem' +import { SidebarNavArrows } from './SidebarNavArrows' import { computeReorder } from './sidebarHooks' import { countByFilter } from '../../utils/noteListHelpers' import { translate, type AppLocale } from '../../lib/i18n' @@ -302,27 +304,55 @@ export function TypesSection({ ) } -export function SidebarTitleBar({ locale = 'en', onCollapse }: { locale?: AppLocale; onCollapse?: () => void }) { +const noopNavigation = () => {} + +export function SidebarTitleBar({ + locale = 'en', + onCollapse, + canGoBack = false, + canGoForward = false, + onGoBack = noopNavigation, + onGoForward = noopNavigation, +}: { + locale?: AppLocale + onCollapse?: () => void + canGoBack?: boolean + canGoForward?: boolean + onGoBack?: () => void + onGoForward?: () => void +}) { const { onMouseDown } = useDragRegion() return ( -
- {onCollapse && ( - - )} -
+ +
+ + {onCollapse && ( + + )} +
+
) } diff --git a/src/components/sidebar/SidebarTitleBar.test.tsx b/src/components/sidebar/SidebarTitleBar.test.tsx new file mode 100644 index 00000000..e3be0a0d --- /dev/null +++ b/src/components/sidebar/SidebarTitleBar.test.tsx @@ -0,0 +1,51 @@ +import type { ComponentProps } from 'react' +import { render, screen, fireEvent } from '@testing-library/react' +import { describe, it, expect, vi } from 'vitest' +import { SidebarTitleBar } from './SidebarSections' + +function renderTitleBar(overrides: Partial> = {}) { + const props = { + canGoBack: true, + canGoForward: true, + onGoBack: vi.fn(), + onGoForward: vi.fn(), + onCollapse: vi.fn(), + ...overrides, + } + return { + ...render(), + props, + } +} + +describe('SidebarTitleBar', () => { + it('renders navigation arrows and the collapse button together', () => { + renderTitleBar() + expect(screen.getByTestId('sidebar-nav-arrows')).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Back' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Forward' })).toBeInTheDocument() + expect(screen.getByRole('button', { name: 'Collapse sidebar' })).toBeInTheDocument() + }) + + it('forwards back and forward clicks', () => { + const { props } = renderTitleBar() + fireEvent.click(screen.getByRole('button', { name: 'Back' })) + fireEvent.click(screen.getByRole('button', { name: 'Forward' })) + expect(props.onGoBack).toHaveBeenCalledOnce() + expect(props.onGoForward).toHaveBeenCalledOnce() + }) + + it('keeps the collapse button clickable and outside the drag region', () => { + const { props } = renderTitleBar() + const collapse = screen.getByRole('button', { name: 'Collapse sidebar' }) + fireEvent.click(collapse) + expect(props.onCollapse).toHaveBeenCalledOnce() + expect(collapse).toHaveAttribute('data-no-drag') + }) + + it('hides collapse when unavailable but keeps history controls visible', () => { + renderTitleBar({ onCollapse: undefined }) + expect(screen.getByTestId('sidebar-nav-arrows')).toBeInTheDocument() + expect(screen.queryByRole('button', { name: 'Collapse sidebar' })).not.toBeInTheDocument() + }) +}) diff --git a/src/lib/locales/de-DE.json b/src/lib/locales/de-DE.json index 209ffadf..fd0b6bf7 100644 --- a/src/lib/locales/de-DE.json +++ b/src/lib/locales/de-DE.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "Posteingang", "sidebar.nav.allNotes": "Alle Notizen", "sidebar.nav.archive": "Archivieren", + "sidebar.nav.back": "Zurück", + "sidebar.nav.forward": "Weiter", "sidebar.group.favorites": "FAVORITEN", "sidebar.group.views": "AUFRUFE", "sidebar.group.types": "TYPEN", diff --git a/src/lib/locales/en.json b/src/lib/locales/en.json index 148e9529..9bd0097e 100644 --- a/src/lib/locales/en.json +++ b/src/lib/locales/en.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "Inbox", "sidebar.nav.allNotes": "All Notes", "sidebar.nav.archive": "Archive", + "sidebar.nav.back": "Back", + "sidebar.nav.forward": "Forward", "sidebar.group.favorites": "FAVORITES", "sidebar.group.views": "VIEWS", "sidebar.group.types": "TYPES", diff --git a/src/lib/locales/es-419.json b/src/lib/locales/es-419.json index dec68def..b34d1ffe 100644 --- a/src/lib/locales/es-419.json +++ b/src/lib/locales/es-419.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "Bandeja de entrada", "sidebar.nav.allNotes": "Todas las notas", "sidebar.nav.archive": "Archivar", + "sidebar.nav.back": "Atrás", + "sidebar.nav.forward": "Siguiente", "sidebar.group.favorites": "FAVORITOS", "sidebar.group.views": "VISTAS", "sidebar.group.types": "TIPOS", diff --git a/src/lib/locales/es-ES.json b/src/lib/locales/es-ES.json index ef04108e..aaa5dd45 100644 --- a/src/lib/locales/es-ES.json +++ b/src/lib/locales/es-ES.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "Bandeja de entrada", "sidebar.nav.allNotes": "Todas las notas", "sidebar.nav.archive": "Archivar", + "sidebar.nav.back": "Atrás", + "sidebar.nav.forward": "Siguiente", "sidebar.group.favorites": "FAVORITOS", "sidebar.group.views": "VISUALIZACIONES", "sidebar.group.types": "TIPOS", diff --git a/src/lib/locales/fr-FR.json b/src/lib/locales/fr-FR.json index 776a9b81..13b0ffb1 100644 --- a/src/lib/locales/fr-FR.json +++ b/src/lib/locales/fr-FR.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "Boîte de réception", "sidebar.nav.allNotes": "Toutes les notes", "sidebar.nav.archive": "Archiver", + "sidebar.nav.back": "Retour", + "sidebar.nav.forward": "Suivant", "sidebar.group.favorites": "FAVORIS", "sidebar.group.views": "VUES", "sidebar.group.types": "TYPES", diff --git a/src/lib/locales/it-IT.json b/src/lib/locales/it-IT.json index b2364e3d..29b6fc61 100644 --- a/src/lib/locales/it-IT.json +++ b/src/lib/locales/it-IT.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "Inbox", "sidebar.nav.allNotes": "Tutte le note", "sidebar.nav.archive": "Archiviate", + "sidebar.nav.back": "Indietro", + "sidebar.nav.forward": "Avanti", "sidebar.group.favorites": "PREFERITI", "sidebar.group.views": "VISTE", "sidebar.group.types": "TIPI", diff --git a/src/lib/locales/ja-JP.json b/src/lib/locales/ja-JP.json index 70549b10..556bdf6b 100644 --- a/src/lib/locales/ja-JP.json +++ b/src/lib/locales/ja-JP.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "受信トレイ", "sidebar.nav.allNotes": "すべてのノート", "sidebar.nav.archive": "アーカイブ", + "sidebar.nav.back": "戻る", + "sidebar.nav.forward": "進む", "sidebar.group.favorites": "お気に入り", "sidebar.group.views": "閲覧数", "sidebar.group.types": "タイプ", diff --git a/src/lib/locales/ko-KR.json b/src/lib/locales/ko-KR.json index 4c160593..54104d20 100644 --- a/src/lib/locales/ko-KR.json +++ b/src/lib/locales/ko-KR.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "받은 편지함", "sidebar.nav.allNotes": "모든 노트", "sidebar.nav.archive": "보관", + "sidebar.nav.back": "뒤로", + "sidebar.nav.forward": "다음", "sidebar.group.favorites": "즐겨찾기", "sidebar.group.views": "조회수", "sidebar.group.types": "유형", diff --git a/src/lib/locales/pt-BR.json b/src/lib/locales/pt-BR.json index be6db5e8..62e65cb9 100644 --- a/src/lib/locales/pt-BR.json +++ b/src/lib/locales/pt-BR.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "Caixa de entrada", "sidebar.nav.allNotes": "Todas as anotações", "sidebar.nav.archive": "Arquivar", + "sidebar.nav.back": "Voltar", + "sidebar.nav.forward": "Avançar", "sidebar.group.favorites": "FAVORITOS", "sidebar.group.views": "VISUALIZAÇÕES", "sidebar.group.types": "TIPOS", diff --git a/src/lib/locales/pt-PT.json b/src/lib/locales/pt-PT.json index 6c837f15..86ddb847 100644 --- a/src/lib/locales/pt-PT.json +++ b/src/lib/locales/pt-PT.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "Caixa de entrada", "sidebar.nav.allNotes": "Todas as notas", "sidebar.nav.archive": "Arquivar", + "sidebar.nav.back": "Voltar", + "sidebar.nav.forward": "Avançar", "sidebar.group.favorites": "FAVORITOS", "sidebar.group.views": "VISUALIZAÇÕES", "sidebar.group.types": "TIPOS", diff --git a/src/lib/locales/ru-RU.json b/src/lib/locales/ru-RU.json index c039e786..3c80d36c 100644 --- a/src/lib/locales/ru-RU.json +++ b/src/lib/locales/ru-RU.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "Входящие", "sidebar.nav.allNotes": "Все заметки", "sidebar.nav.archive": "Архив", + "sidebar.nav.back": "Назад", + "sidebar.nav.forward": "Вперед", "sidebar.group.favorites": "ИЗБРАННОЕ", "sidebar.group.views": "ПРОСМОТРЫ", "sidebar.group.types": "ТИПЫ", diff --git a/src/lib/locales/zh-CN.json b/src/lib/locales/zh-CN.json index 70c2e3c0..9045bb4a 100644 --- a/src/lib/locales/zh-CN.json +++ b/src/lib/locales/zh-CN.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "收件箱", "sidebar.nav.allNotes": "全部笔记", "sidebar.nav.archive": "归档", + "sidebar.nav.back": "返回", + "sidebar.nav.forward": "前进", "sidebar.group.favorites": "收藏", "sidebar.group.views": "视图", "sidebar.group.types": "类型", diff --git a/src/lib/locales/zh-TW.json b/src/lib/locales/zh-TW.json index 6329dc12..11210d8c 100644 --- a/src/lib/locales/zh-TW.json +++ b/src/lib/locales/zh-TW.json @@ -245,6 +245,8 @@ "sidebar.nav.inbox": "收件箱", "sidebar.nav.allNotes": "全部筆記", "sidebar.nav.archive": "歸檔", + "sidebar.nav.back": "返回", + "sidebar.nav.forward": "下一頁", "sidebar.group.favorites": "收藏", "sidebar.group.views": "檢視", "sidebar.group.types": "型別", diff --git a/src/lib/productAnalytics.ts b/src/lib/productAnalytics.ts index 3b1d6936..4f1aae86 100644 --- a/src/lib/productAnalytics.ts +++ b/src/lib/productAnalytics.ts @@ -7,6 +7,7 @@ import type { FilePreviewKind } from '../utils/filePreview' type TrackedPreviewKind = FilePreviewKind | 'unsupported' type FilePreviewAction = 'copy_path' | 'open_external' | 'reveal' type AgentBlockedReason = 'agent_unavailable' | 'missing_vault' +type NavigationHistoryDirection = 'back' | 'forward' const ALL_NOTES_VISIBILITY_CATEGORIES: ReadonlyArray = [ 'pdfs', @@ -52,6 +53,10 @@ export function trackAllNotesVisibilityChanged( } } +export function trackNavigationHistoryButtonClicked(direction: NavigationHistoryDirection): void { + trackEvent('navigation_history_button_clicked', { direction }) +} + export function trackAiAgentMessageBlocked(agent: AiAgentId, reason: AgentBlockedReason): void { trackEvent('ai_agent_message_blocked', { agent, reason }) }