diff --git a/apps/daemon/src/project-routes.ts b/apps/daemon/src/project-routes.ts index d7e2561db..48fbc8e7f 100644 --- a/apps/daemon/src/project-routes.ts +++ b/apps/daemon/src/project-routes.ts @@ -21,6 +21,106 @@ import { auditDesignSystemPackage } from './tools-connectors-cli.js'; export interface RegisterProjectRoutesDeps extends RouteDeps<'db' | 'design' | 'http' | 'paths' | 'projectStore' | 'projectFiles' | 'conversations' | 'templates' | 'status' | 'events' | 'ids' | 'telemetry' | 'validation'> {} +const URL_PREVIEW_SCROLL_BRIDGE = ``; + +function wantsUrlPreviewScrollBridge(value: unknown): boolean { + if (Array.isArray(value)) return value.some(wantsUrlPreviewScrollBridge); + if (typeof value !== 'string') return false; + return value === 'scroll' || value === '1' || value === 'true'; +} + +function injectUrlPreviewScrollBridge(html: string): string { + if (html.includes('data-od-url-scroll-bridge')) return html; + const bodyCloseIndex = html.search(/<\/body\s*>/i); + if (bodyCloseIndex >= 0) { + return `${html.slice(0, bodyCloseIndex)}${URL_PREVIEW_SCROLL_BRIDGE}${html.slice(bodyCloseIndex)}`; + } + return `${html}${URL_PREVIEW_SCROLL_BRIDGE}`; +} + export function registerProjectRoutes(app: Express, ctx: RegisterProjectRoutesDeps) { const { db, design } = ctx; const { sendApiError, createSseResponse } = ctx.http; @@ -959,6 +1059,13 @@ export function registerProjectFileRoutes(app: Express, ctx: RegisterProjectFile } const file = await readProjectFile(PROJECTS_DIR, projectId, relPath, project?.metadata); + if ( + wantsUrlPreviewScrollBridge(req.query.odPreviewBridge) && + /^text\/html(?:;|$)/i.test(file.mime) + ) { + res.type(file.mime).send(injectUrlPreviewScrollBridge(file.buffer.toString('utf8'))); + return; + } res.type(file.mime).send(file.buffer); } catch (err: any) { const status = err && err.code === 'ENOENT' ? 404 : 400; diff --git a/apps/daemon/tests/project-file-range.test.ts b/apps/daemon/tests/project-file-range.test.ts index 45b9d1199..8fcd8abb2 100644 --- a/apps/daemon/tests/project-file-range.test.ts +++ b/apps/daemon/tests/project-file-range.test.ts @@ -165,6 +165,11 @@ describe('GET /api/projects/:id/raw/* range request route', () => { await writeFile(path.join(dir, 'clip.mp4'), Buffer.alloc(FILE_SIZE, 0x42)); await writeFile(path.join(dir, 'audio.mp3'), Buffer.alloc(FILE_SIZE, 0x43)); await writeFile(path.join(dir, 'page.html'), Buffer.from('
')); + await writeFile(path.join(dir, 'body.html'), Buffer.from('