mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
feat(desktop): add handlePreviewLink action dispatcher
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8c4e8073ab
commit
13222b375d
66
desktop/src/lib/handlePreviewLink.test.ts
Normal file
66
desktop/src/lib/handlePreviewLink.test.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { handlePreviewLink, type PreviewLinkDeps } from './handlePreviewLink'
|
||||
|
||||
function makeDeps(overrides?: Partial<PreviewLinkDeps>): PreviewLinkDeps {
|
||||
return {
|
||||
sessionId: 's1',
|
||||
serverBaseUrl: 'http://127.0.0.1:8787',
|
||||
openBrowser: vi.fn(),
|
||||
openFilePreview: vi.fn(),
|
||||
openExternal: vi.fn(),
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
describe('handlePreviewLink', () => {
|
||||
it('routes loopback urls to openBrowser with the url', () => {
|
||||
const deps = makeDeps()
|
||||
const handled = handlePreviewLink('http://localhost:5173/', deps)
|
||||
expect(handled).toBe(true)
|
||||
expect(deps.openBrowser).toHaveBeenCalledWith('s1', 'http://localhost:5173/')
|
||||
expect(deps.openFilePreview).not.toHaveBeenCalled()
|
||||
expect(deps.openExternal).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('routes absolute html file paths to openBrowser with the preview-fs url', () => {
|
||||
const deps = makeDeps()
|
||||
const handled = handlePreviewLink('/Users/x/index.html', deps)
|
||||
expect(handled).toBe(true)
|
||||
// previewFsUrl preserves the leading slash, yielding an intentional `//`
|
||||
// between <sessionId> and the absolute path so the server resolves it as
|
||||
// an absolute-within-workspace path.
|
||||
expect(deps.openBrowser).toHaveBeenCalledWith(
|
||||
's1',
|
||||
'http://127.0.0.1:8787/preview-fs/s1//Users/x/index.html',
|
||||
)
|
||||
expect(deps.openFilePreview).not.toHaveBeenCalled()
|
||||
expect(deps.openExternal).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('routes relative previewable docs to openFilePreview with the relative path', () => {
|
||||
const deps = makeDeps()
|
||||
const handled = handlePreviewLink('docs/report.md', deps)
|
||||
expect(handled).toBe(true)
|
||||
expect(deps.openFilePreview).toHaveBeenCalledWith('s1', 'docs/report.md')
|
||||
expect(deps.openBrowser).not.toHaveBeenCalled()
|
||||
expect(deps.openExternal).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('routes remote http(s) to openExternal with the url', () => {
|
||||
const deps = makeDeps()
|
||||
const handled = handlePreviewLink('https://example.com/', deps)
|
||||
expect(handled).toBe(true)
|
||||
expect(deps.openExternal).toHaveBeenCalledWith('https://example.com/')
|
||||
expect(deps.openBrowser).not.toHaveBeenCalled()
|
||||
expect(deps.openFilePreview).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns false for ignored links and calls no deps', () => {
|
||||
const deps = makeDeps()
|
||||
const handled = handlePreviewLink('#x', deps)
|
||||
expect(handled).toBe(false)
|
||||
expect(deps.openBrowser).not.toHaveBeenCalled()
|
||||
expect(deps.openFilePreview).not.toHaveBeenCalled()
|
||||
expect(deps.openExternal).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
43
desktop/src/lib/handlePreviewLink.ts
Normal file
43
desktop/src/lib/handlePreviewLink.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { classifyPreviewLink } from './previewLinkRouter'
|
||||
|
||||
export type PreviewLinkDeps = {
|
||||
sessionId: string
|
||||
serverBaseUrl: string
|
||||
openBrowser: (sessionId: string, url: string) => void
|
||||
openFilePreview: (sessionId: string, path: string) => void
|
||||
openExternal: (url: string) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a `/preview-fs/<sessionId>/<path>` URL for the local server.
|
||||
*
|
||||
* Absolute file paths (leading slash) are preserved as-is, so the resulting URL
|
||||
* carries a `//` between `<sessionId>` and the path. That double slash is
|
||||
* intentional: the server slices everything after the `<sessionId>` segment and
|
||||
* runs `path.resolve(workDir, relPath)`, so an absolute path is resolved as an
|
||||
* absolute-within-workspace path and sandbox-checked against the work dir root.
|
||||
*/
|
||||
function previewFsUrl(base: string, sessionId: string, filePath: string): string {
|
||||
return `${base.replace(/\/$/, '')}/preview-fs/${encodeURIComponent(sessionId)}/${filePath.replace(/^\/+/, '/')}`
|
||||
}
|
||||
|
||||
/** Returns true if handled (caller should preventDefault). */
|
||||
export function handlePreviewLink(href: string, deps: PreviewLinkDeps): boolean {
|
||||
const cls = classifyPreviewLink(href)
|
||||
switch (cls.kind) {
|
||||
case 'browser-localhost':
|
||||
deps.openBrowser(deps.sessionId, cls.url!)
|
||||
return true
|
||||
case 'browser-file':
|
||||
deps.openBrowser(deps.sessionId, previewFsUrl(deps.serverBaseUrl, deps.sessionId, cls.path!))
|
||||
return true
|
||||
case 'file-preview':
|
||||
deps.openFilePreview(deps.sessionId, cls.path!)
|
||||
return true
|
||||
case 'remote':
|
||||
deps.openExternal(cls.url!)
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user