diff --git a/apps/desktop/src/app/right-sidebar/file-actions.test.tsx b/apps/desktop/src/app/right-sidebar/file-actions.test.tsx
new file mode 100644
index 0000000000..e7ebb04cd6
--- /dev/null
+++ b/apps/desktop/src/app/right-sidebar/file-actions.test.tsx
@@ -0,0 +1,50 @@
+import { cleanup, fireEvent, render, screen } from '@testing-library/react'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+
+import { I18nProvider } from '@/i18n'
+
+const { openFileWithSystemApp } = vi.hoisted(() => ({ openFileWithSystemApp: vi.fn() }))
+
+vi.mock('@/lib/desktop-fs', () => ({ isDesktopFsRemoteMode: () => false }))
+vi.mock('@/lib/office-files', () => ({ openFileWithSystemApp }))
+
+import { FileEntryContextMenu } from './file-actions'
+
+function renderMenu(isDirectory = false) {
+ return render(
+
+
+
+
+
+ )
+}
+
+beforeEach(() => {
+ openFileWithSystemApp.mockReset().mockResolvedValue(true)
+})
+
+afterEach(cleanup)
+
+describe('FileEntryContextMenu system open action', () => {
+ it('opens files with the system app', async () => {
+ renderMenu()
+
+ fireEvent.contextMenu(screen.getByRole('button', { name: 'Row' }))
+ fireEvent.click(await screen.findByRole('menuitem', { name: '用系统应用打开' }))
+
+ expect(openFileWithSystemApp).toHaveBeenCalledWith('/w/notes.md')
+ })
+
+ it('does not offer system open for directories', () => {
+ renderMenu(true)
+
+ fireEvent.contextMenu(screen.getByRole('button', { name: 'Row' }))
+
+ expect(screen.queryByRole('menuitem', { name: '用系统应用打开' })).toBeNull()
+ })
+})
diff --git a/apps/desktop/src/app/right-sidebar/file-actions.tsx b/apps/desktop/src/app/right-sidebar/file-actions.tsx
index 9a0853634d..405e5ce408 100644
--- a/apps/desktop/src/app/right-sidebar/file-actions.tsx
+++ b/apps/desktop/src/app/right-sidebar/file-actions.tsx
@@ -13,6 +13,7 @@ import { translateNow, useI18n } from '@/i18n'
import { isDesktopFsRemoteMode } from '@/lib/desktop-fs'
import { isSubmitEnter } from '@/lib/ime'
import { IS_MAC } from '@/lib/keybinds/combo'
+import { openFileWithSystemApp } from '@/lib/office-files'
import { cn } from '@/lib/utils'
import {
$fileActionDialog,
@@ -73,6 +74,14 @@ export function FileEntryContextMenu({ children, isDirectory, name, path, relati
{/* Don't restore focus to the row on close: "Rename" mounts an autofocused
inline input, and the default focus-return would blur it immediately. */}
event.preventDefault()}>
+ {!isDirectory && (
+ <>
+ void openFileWithSystemApp(path)}>
+ {t.preview.openWithSystemApp}
+
+
+ >
+ )}
{localFs && (
<>
void revealFile(path)}>{revealLabel}