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)}`;
+}