mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-14 03:09:44 +02:00
refactor(file-preview): one rendersAsPage() for the terminal and the preview
What counts as an HTML page lived in three places: HTML_VIEW_EXTENSIONS on the server, a /\.html?$/ regex in the terminal link provider and a HTML_PAGE_EXTS set in panels-ui.js. The two frontend copies are now one rendersAsPage() next to previewsInFileViewer() in constants.js, used by the link provider (and through it every grid tile and the split's Pane B, which share registerFilePathLinkProvider) and by openFilePreview() and its toolbar. A new test pins it equal to the server's list. toggleFilePreviewPage() now returns the preview load, so a caller (and the coming frontend test) can await the flip. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1976,6 +1976,21 @@ function previewsInFileViewer(filePath) {
|
||||
return FILE_PREVIEW_EXTENSIONS.has(ext);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extensions the file preview renders as a PAGE, from the sandboxed
|
||||
* `/html-view/<cap>/` route (src/web/routes/html-view-routes.ts, whose
|
||||
* HTML_VIEW_EXTENSIONS this mirrors; test/file-preview-html-page.test.ts pins
|
||||
* the two equal). The terminal sends a click on one to the preview, never to
|
||||
* the log viewer, and the preview renders it with a Page pill back to source.
|
||||
*/
|
||||
const HTML_PAGE_EXTENSIONS = new Set(['html', 'htm']);
|
||||
|
||||
/** Whether a path names a file the preview renders as a page ({@link HTML_PAGE_EXTENSIONS}). */
|
||||
function rendersAsPage(filePath) {
|
||||
const ext = String(filePath || '').split('.').pop().toLowerCase();
|
||||
return HTML_PAGE_EXTENSIONS.has(ext);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The LOGICAL line a terminal row belongs to — the rows it spans, its text as one
|
||||
@@ -2992,7 +3007,13 @@ function describeSessionHarness(session, catalog) {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.CodemanSessionHarness = { describeSessionHarness, SESSION_MODEL_MAX_CHARS };
|
||||
window.CodemanHistoryFormat = { formatHistoryBytes, computeHistoryTruncationNotice, computeRewriteScrollLine };
|
||||
window.CodemanFilePaths = { absoluteFilePathPattern, previewsInFileViewer, FILE_PREVIEW_EXTENSIONS };
|
||||
window.CodemanFilePaths = {
|
||||
absoluteFilePathPattern,
|
||||
previewsInFileViewer,
|
||||
FILE_PREVIEW_EXTENSIONS,
|
||||
rendersAsPage,
|
||||
HTML_PAGE_EXTENSIONS,
|
||||
};
|
||||
window.CodemanTerminalLines = { terminalLogicalLine };
|
||||
window.CodemanUrlSession = { sessionIdFromFragment };
|
||||
window.CodemanSplitPane = {
|
||||
|
||||
@@ -24,7 +24,6 @@ const TEXT_PREVIEW_MAX_LINES = 500;
|
||||
// cap: a rendered README cut mid-way reads as the whole document.
|
||||
const MARKDOWN_PREVIEW_MAX_LINES = 10000;
|
||||
const MARKDOWN_EXTS = new Set(['md', 'markdown']);
|
||||
const HTML_PAGE_EXTS = new Set(['html', 'htm']);
|
||||
// File Viewer text-view prefs: per-device, in their own localStorage keys for
|
||||
// the same reason as FILE_BROWSER_SHOW_HIDDEN_KEY (the app-settings object is
|
||||
// rebuilt from the settings modal on save, so a key toggled from the viewer
|
||||
@@ -4126,7 +4125,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
// The Page pill switches to the source view below (with its Edit button), and
|
||||
// a refused mint (remote case, broad folder, hidden directory) falls through
|
||||
// to that source view on its own.
|
||||
if (!attachmentId && HTML_PAGE_EXTS.has(ext) && this._filePreviewPref('htmlRendered')) {
|
||||
if (!attachmentId && rendersAsPage(filePath) && this._filePreviewPref('htmlRendered')) {
|
||||
const pageUrl = await this._mintHtmlViewUrl(filePath, sessionId);
|
||||
if (pageUrl) {
|
||||
footerEl.textContent = 'HTML';
|
||||
@@ -4615,7 +4614,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
const htmlSource =
|
||||
view === 'text' &&
|
||||
!this.filePreviewPageRefused &&
|
||||
HTML_PAGE_EXTS.has(text?.ext || '') &&
|
||||
rendersAsPage(text?.filePath) &&
|
||||
(!text.attachmentId || text.filePath.startsWith('/'));
|
||||
set('filePreviewPageBtn', view === 'page' || htmlSource, view === 'page');
|
||||
set('filePreviewLinesBtn', view === 'text', this._filePreviewPref('lineNumbers'));
|
||||
@@ -4642,7 +4641,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
const target = this.filePreviewPage || this.filePreviewText;
|
||||
if (!target) return;
|
||||
this._setFilePreviewPref('htmlRendered', !this._filePreviewPref('htmlRendered'));
|
||||
this.openFilePreview(target.filePath, target.sessionId);
|
||||
return this.openFilePreview(target.filePath, target.sessionId);
|
||||
},
|
||||
|
||||
toggleFilePreviewMd() {
|
||||
|
||||
@@ -2091,7 +2091,7 @@ Object.assign(CodemanApp.prototype, {
|
||||
const sessionId = getSessionId();
|
||||
// An HTML file renders as a page in the preview, never as source
|
||||
// in the log viewer.
|
||||
if (previewsInFileViewer(text) || /\.html?$/i.test(text) || self._isExternalPreviewPath(text, sessionId)) {
|
||||
if (previewsInFileViewer(text) || rendersAsPage(text) || self._isExternalPreviewPath(text, sessionId)) {
|
||||
self.openFilePreview(text, sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* @fileoverview File Viewer: an `.html` file renders as a sandboxed page.
|
||||
*
|
||||
* The server half (`/html-view/<cap>/...`, html-view-routes.ts) is pinned by
|
||||
* test/routes/html-view-routes.test.ts. This file pins the frontend half:
|
||||
*
|
||||
* 1. `rendersAsPage()` (constants.js) and the server's HTML_VIEW_EXTENSIONS
|
||||
* name the same extensions, so the terminal, the preview and the route
|
||||
* agree on what a page is.
|
||||
*
|
||||
* Loaded via `vm` with a jsdom document injected, like
|
||||
* file-preview-markdown.test.ts: constants.js + panels-ui.js only.
|
||||
*/
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import vm from 'node:vm';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { HTML_VIEW_EXTENSIONS } from '../src/web/routes/html-view-routes.js';
|
||||
|
||||
const PUBLIC = resolve(import.meta.dirname, '../src/web/public');
|
||||
const constantsJs = readFileSync(resolve(PUBLIC, 'constants.js'), 'utf8');
|
||||
|
||||
describe('rendersAsPage', () => {
|
||||
it('names exactly the extensions the html-view route renders', () => {
|
||||
const context = vm.createContext({});
|
||||
vm.runInContext(`${constantsJs}\nglobalThis.__page = { rendersAsPage, HTML_PAGE_EXTENSIONS };`, context, {
|
||||
filename: 'constants.js',
|
||||
});
|
||||
const { rendersAsPage, HTML_PAGE_EXTENSIONS } = (
|
||||
context as { __page: { rendersAsPage: (p: string) => boolean; HTML_PAGE_EXTENSIONS: Set<string> } }
|
||||
).__page;
|
||||
|
||||
expect([...HTML_PAGE_EXTENSIONS].sort()).toEqual([...HTML_VIEW_EXTENSIONS].sort());
|
||||
expect(rendersAsPage('/tmp/out/report.html')).toBe(true);
|
||||
expect(rendersAsPage('docs/INDEX.HTM')).toBe(true);
|
||||
expect(rendersAsPage('notes.md')).toBe(false);
|
||||
expect(rendersAsPage('page.html.txt')).toBe(false);
|
||||
expect(rendersAsPage(undefined as unknown as string)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user