mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-08-03 17:01:15 +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