From 35a49ef310b90bc73a387239bdc8cf72621f8648 Mon Sep 17 00:00:00 2001 From: zhouzhou626 <255877794+zhouzhou626@users.noreply.github.com> Date: Wed, 20 May 2026 03:21:02 +0800 Subject: [PATCH] Apply dark theme to HTML preview pane --- src/__test__/html-preview.test.ts | 37 ++++++++++++++++++++++ src/editor.tsx | 9 ++++-- src/html-preview.ts | 52 +++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/__test__/html-preview.test.ts create mode 100644 src/html-preview.ts diff --git a/src/__test__/html-preview.test.ts b/src/__test__/html-preview.test.ts new file mode 100644 index 0000000..55baa33 --- /dev/null +++ b/src/__test__/html-preview.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest'; +import { applyHtmlPreviewColorMode } from '../html-preview'; + +describe('applyHtmlPreviewColorMode', () => { + it('leaves light mode previews unchanged', () => { + const srcDoc = '

Hello

'; + + expect(applyHtmlPreviewColorMode(srcDoc, 'light')).toBe(srcDoc); + }); + + it('adds dark preview styles to document fragments', () => { + const srcDoc = '

Hello

'; + const themedSrcDoc = applyHtmlPreviewColorMode(srcDoc, 'dark'); + + expect(themedSrcDoc).toContain('data-php-playground-preview-theme'); + expect(themedSrcDoc).toContain('background-color: rgb(30, 30, 30)'); + expect(themedSrcDoc).toContain('color: #d4d4d4'); + expect(themedSrcDoc.endsWith(srcDoc)).toBe(true); + }); + + it('injects dark preview styles inside the head when present', () => { + const srcDoc = + 'xx'; + const themedSrcDoc = applyHtmlPreviewColorMode(srcDoc, 'dark'); + + expect(themedSrcDoc).toContain( + '`; + +export function applyHtmlPreviewColorMode( + srcDoc: string, + colorMode: PreviewColorMode +): string { + if (colorMode !== 'dark') { + return srcDoc; + } + if (srcDoc.includes('data-php-playground-preview-theme')) { + return srcDoc; + } + return insertPreviewStyle(srcDoc, darkHtmlPreviewStyle); +} + +function insertPreviewStyle(srcDoc: string, style: string): string { + const headMatch = srcDoc.match(/]*)?>/i); + if (headMatch?.index !== undefined) { + return insertAt(srcDoc, headMatch.index + headMatch[0].length, style); + } + + const htmlMatch = srcDoc.match(/]*)?>/i); + if (htmlMatch?.index !== undefined) { + return insertAt(srcDoc, htmlMatch.index + htmlMatch[0].length, style); + } + + const doctypeMatch = srcDoc.match(/^\s*]*>/i); + if (doctypeMatch?.index !== undefined) { + return insertAt( + srcDoc, + doctypeMatch.index + doctypeMatch[0].length, + style + ); + } + + return `${style}${srcDoc}`; +} + +function insertAt(value: string, index: number, insertion: string): string { + return `${value.slice(0, index)}${insertion}${value.slice(index)}`; +}