feat: Improve DiffView theme and color consistency
Browse files- Added dark/light theme support for syntax highlighting
- Enhanced color styles for added/removed lines and characters
- Integrated theme store to dynamically adjust syntax highlighter theme
- Refined color contrast for better readability across themes
app/components/workbench/DiffView.tsx
CHANGED
@@ -10,6 +10,7 @@ import { diffFiles, extractRelativePath } from '~/utils/diff';
|
|
10 |
import { ActionRunner } from '~/lib/runtime/action-runner';
|
11 |
import type { FileHistory } from '~/types/actions';
|
12 |
import { getLanguageFromExtension } from '~/utils/getLanguageFromExtension';
|
|
|
13 |
|
14 |
interface CodeComparisonProps {
|
15 |
beforeCode: string;
|
@@ -302,12 +303,20 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|
302 |
const lineNumberStyles = "w-9 shrink-0 pl-2 py-1 text-left font-mono text-bolt-elements-textTertiary border-r border-bolt-elements-borderColor bg-bolt-elements-background-depth-1";
|
303 |
const lineContentStyles = "px-1 py-1 font-mono whitespace-pre flex-1 group-hover:bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary";
|
304 |
const diffPanelStyles = "h-full overflow-auto diff-panel-content";
|
|
|
|
|
305 |
const diffLineStyles = {
|
306 |
-
added: 'bg-green-500/20 border-l-4 border-green-500',
|
307 |
-
removed: 'bg-red-500/20 border-l-4 border-red-500',
|
308 |
unchanged: ''
|
309 |
};
|
310 |
|
|
|
|
|
|
|
|
|
|
|
|
|
311 |
const renderContentWarning = (type: 'binary' | 'error') => (
|
312 |
<div className="h-full flex items-center justify-center p-4">
|
313 |
<div className="text-center text-bolt-elements-textTertiary">
|
@@ -324,10 +333,11 @@ const renderContentWarning = (type: 'binary' | 'error') => (
|
|
324 |
</div>
|
325 |
);
|
326 |
|
327 |
-
const NoChangesView = memo(({ beforeCode, language, highlighter }: {
|
328 |
beforeCode: string;
|
329 |
language: string;
|
330 |
highlighter: any;
|
|
|
331 |
}) => (
|
332 |
<div className="h-full flex flex-col items-center justify-center p-4">
|
333 |
<div className="text-center text-bolt-elements-textTertiary">
|
@@ -347,7 +357,7 @@ const NoChangesView = memo(({ beforeCode, language, highlighter }: {
|
|
347 |
<span className="mr-2"> </span>
|
348 |
<span dangerouslySetInnerHTML={{
|
349 |
__html: highlighter ?
|
350 |
-
highlighter.codeToHtml(line, { lang: language, theme: 'github-dark' })
|
351 |
.replace(/<\/?pre[^>]*>/g, '')
|
352 |
.replace(/<\/?code[^>]*>/g, '')
|
353 |
: line
|
@@ -372,7 +382,8 @@ const CodeLine = memo(({
|
|
372 |
type,
|
373 |
highlighter,
|
374 |
language,
|
375 |
-
block
|
|
|
376 |
}: {
|
377 |
lineNumber: number;
|
378 |
content: string;
|
@@ -380,17 +391,14 @@ const CodeLine = memo(({
|
|
380 |
highlighter: any;
|
381 |
language: string;
|
382 |
block: DiffBlock;
|
|
|
383 |
}) => {
|
384 |
-
const bgColor =
|
385 |
-
added: 'bg-green-500/20 border-l-4 border-green-500',
|
386 |
-
removed: 'bg-red-500/20 border-l-4 border-red-500',
|
387 |
-
unchanged: ''
|
388 |
-
}[type];
|
389 |
|
390 |
const renderContent = () => {
|
391 |
if (type === 'unchanged' || !block.charChanges) {
|
392 |
const highlightedCode = highlighter ?
|
393 |
-
highlighter.codeToHtml(content, { lang: language, theme: 'github-dark' })
|
394 |
.replace(/<\/?pre[^>]*>/g, '')
|
395 |
.replace(/<\/?code[^>]*>/g, '')
|
396 |
: content;
|
@@ -400,14 +408,10 @@ const CodeLine = memo(({
|
|
400 |
return (
|
401 |
<>
|
402 |
{block.charChanges.map((change, index) => {
|
403 |
-
const changeClass =
|
404 |
-
added: 'text-green-500 bg-green-500/20',
|
405 |
-
removed: 'text-red-500 bg-red-500/20',
|
406 |
-
unchanged: ''
|
407 |
-
}[change.type];
|
408 |
|
409 |
const highlightedCode = highlighter ?
|
410 |
-
highlighter.codeToHtml(change.value, { lang: language, theme: 'github-dark' })
|
411 |
.replace(/<\/?pre[^>]*>/g, '')
|
412 |
.replace(/<\/?code[^>]*>/g, '')
|
413 |
: change.value;
|
@@ -429,8 +433,8 @@ const CodeLine = memo(({
|
|
429 |
<div className={lineNumberStyles}>{lineNumber + 1}</div>
|
430 |
<div className={`${lineContentStyles} ${bgColor}`}>
|
431 |
<span className="mr-2 text-bolt-elements-textTertiary">
|
432 |
-
{type === 'added' &&
|
433 |
-
{type === 'removed' &&
|
434 |
{type === 'unchanged' && ' '}
|
435 |
</span>
|
436 |
{renderContent()}
|
@@ -488,20 +492,20 @@ const FileInfo = memo(({
|
|
488 |
{showStats && (
|
489 |
<div className="flex items-center gap-1 text-xs">
|
490 |
{additions > 0 && (
|
491 |
-
<span className="text-green-500">+{additions}</span>
|
492 |
)}
|
493 |
{deletions > 0 && (
|
494 |
-
<span className="text-red-500">-{deletions}</span>
|
495 |
)}
|
496 |
</div>
|
497 |
)}
|
498 |
-
<span className="text-yellow-400">Modified</span>
|
499 |
<span className="text-bolt-elements-textTertiary text-xs">
|
500 |
{new Date().toLocaleTimeString()}
|
501 |
</span>
|
502 |
</>
|
503 |
) : (
|
504 |
-
<span className="text-green-400">No Changes</span>
|
505 |
)}
|
506 |
<FullscreenButton onClick={onToggleFullscreen} isFullscreen={isFullscreen} />
|
507 |
</span>
|
@@ -512,6 +516,7 @@ const FileInfo = memo(({
|
|
512 |
const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language, lightTheme, darkTheme }: CodeComparisonProps) => {
|
513 |
const [isFullscreen, setIsFullscreen] = useState(false);
|
514 |
const [highlighter, setHighlighter] = useState<any>(null);
|
|
|
515 |
|
516 |
const toggleFullscreen = useCallback(() => {
|
517 |
setIsFullscreen(prev => !prev);
|
@@ -521,7 +526,7 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language,
|
|
521 |
|
522 |
useEffect(() => {
|
523 |
getHighlighter({
|
524 |
-
themes: ['github-dark'],
|
525 |
langs: ['typescript', 'javascript', 'json', 'html', 'css', 'jsx', 'tsx']
|
526 |
}).then(setHighlighter);
|
527 |
}, []);
|
@@ -551,6 +556,7 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language,
|
|
551 |
highlighter={highlighter}
|
552 |
language={language}
|
553 |
block={block}
|
|
|
554 |
/>
|
555 |
))}
|
556 |
</div>
|
@@ -559,6 +565,7 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language,
|
|
559 |
beforeCode={beforeCode}
|
560 |
language={language}
|
561 |
highlighter={highlighter}
|
|
|
562 |
/>
|
563 |
)}
|
564 |
</div>
|
|
|
10 |
import { ActionRunner } from '~/lib/runtime/action-runner';
|
11 |
import type { FileHistory } from '~/types/actions';
|
12 |
import { getLanguageFromExtension } from '~/utils/getLanguageFromExtension';
|
13 |
+
import { themeStore } from '~/lib/stores/theme';
|
14 |
|
15 |
interface CodeComparisonProps {
|
16 |
beforeCode: string;
|
|
|
303 |
const lineNumberStyles = "w-9 shrink-0 pl-2 py-1 text-left font-mono text-bolt-elements-textTertiary border-r border-bolt-elements-borderColor bg-bolt-elements-background-depth-1";
|
304 |
const lineContentStyles = "px-1 py-1 font-mono whitespace-pre flex-1 group-hover:bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary";
|
305 |
const diffPanelStyles = "h-full overflow-auto diff-panel-content";
|
306 |
+
|
307 |
+
// Updated color styles for better consistency
|
308 |
const diffLineStyles = {
|
309 |
+
added: 'bg-green-500/10 dark:bg-green-500/20 border-l-4 border-green-500',
|
310 |
+
removed: 'bg-red-500/10 dark:bg-red-500/20 border-l-4 border-red-500',
|
311 |
unchanged: ''
|
312 |
};
|
313 |
|
314 |
+
const changeColorStyles = {
|
315 |
+
added: 'text-green-700 dark:text-green-500 bg-green-500/10 dark:bg-green-500/20',
|
316 |
+
removed: 'text-red-700 dark:text-red-500 bg-red-500/10 dark:bg-red-500/20',
|
317 |
+
unchanged: 'text-bolt-elements-textPrimary'
|
318 |
+
};
|
319 |
+
|
320 |
const renderContentWarning = (type: 'binary' | 'error') => (
|
321 |
<div className="h-full flex items-center justify-center p-4">
|
322 |
<div className="text-center text-bolt-elements-textTertiary">
|
|
|
333 |
</div>
|
334 |
);
|
335 |
|
336 |
+
const NoChangesView = memo(({ beforeCode, language, highlighter, theme }: {
|
337 |
beforeCode: string;
|
338 |
language: string;
|
339 |
highlighter: any;
|
340 |
+
theme: string;
|
341 |
}) => (
|
342 |
<div className="h-full flex flex-col items-center justify-center p-4">
|
343 |
<div className="text-center text-bolt-elements-textTertiary">
|
|
|
357 |
<span className="mr-2"> </span>
|
358 |
<span dangerouslySetInnerHTML={{
|
359 |
__html: highlighter ?
|
360 |
+
highlighter.codeToHtml(line, { lang: language, theme: theme === 'dark' ? 'github-dark' : 'github-light' })
|
361 |
.replace(/<\/?pre[^>]*>/g, '')
|
362 |
.replace(/<\/?code[^>]*>/g, '')
|
363 |
: line
|
|
|
382 |
type,
|
383 |
highlighter,
|
384 |
language,
|
385 |
+
block,
|
386 |
+
theme
|
387 |
}: {
|
388 |
lineNumber: number;
|
389 |
content: string;
|
|
|
391 |
highlighter: any;
|
392 |
language: string;
|
393 |
block: DiffBlock;
|
394 |
+
theme: string;
|
395 |
}) => {
|
396 |
+
const bgColor = diffLineStyles[type];
|
|
|
|
|
|
|
|
|
397 |
|
398 |
const renderContent = () => {
|
399 |
if (type === 'unchanged' || !block.charChanges) {
|
400 |
const highlightedCode = highlighter ?
|
401 |
+
highlighter.codeToHtml(content, { lang: language, theme: theme === 'dark' ? 'github-dark' : 'github-light' })
|
402 |
.replace(/<\/?pre[^>]*>/g, '')
|
403 |
.replace(/<\/?code[^>]*>/g, '')
|
404 |
: content;
|
|
|
408 |
return (
|
409 |
<>
|
410 |
{block.charChanges.map((change, index) => {
|
411 |
+
const changeClass = changeColorStyles[change.type];
|
|
|
|
|
|
|
|
|
412 |
|
413 |
const highlightedCode = highlighter ?
|
414 |
+
highlighter.codeToHtml(change.value, { lang: language, theme: theme === 'dark' ? 'github-dark' : 'github-light' })
|
415 |
.replace(/<\/?pre[^>]*>/g, '')
|
416 |
.replace(/<\/?code[^>]*>/g, '')
|
417 |
: change.value;
|
|
|
433 |
<div className={lineNumberStyles}>{lineNumber + 1}</div>
|
434 |
<div className={`${lineContentStyles} ${bgColor}`}>
|
435 |
<span className="mr-2 text-bolt-elements-textTertiary">
|
436 |
+
{type === 'added' && <span className="text-green-700 dark:text-green-500">+</span>}
|
437 |
+
{type === 'removed' && <span className="text-red-700 dark:text-red-500">-</span>}
|
438 |
{type === 'unchanged' && ' '}
|
439 |
</span>
|
440 |
{renderContent()}
|
|
|
492 |
{showStats && (
|
493 |
<div className="flex items-center gap-1 text-xs">
|
494 |
{additions > 0 && (
|
495 |
+
<span className="text-green-700 dark:text-green-500">+{additions}</span>
|
496 |
)}
|
497 |
{deletions > 0 && (
|
498 |
+
<span className="text-red-700 dark:text-red-500">-{deletions}</span>
|
499 |
)}
|
500 |
</div>
|
501 |
)}
|
502 |
+
<span className="text-yellow-600 dark:text-yellow-400">Modified</span>
|
503 |
<span className="text-bolt-elements-textTertiary text-xs">
|
504 |
{new Date().toLocaleTimeString()}
|
505 |
</span>
|
506 |
</>
|
507 |
) : (
|
508 |
+
<span className="text-green-700 dark:text-green-400">No Changes</span>
|
509 |
)}
|
510 |
<FullscreenButton onClick={onToggleFullscreen} isFullscreen={isFullscreen} />
|
511 |
</span>
|
|
|
516 |
const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language, lightTheme, darkTheme }: CodeComparisonProps) => {
|
517 |
const [isFullscreen, setIsFullscreen] = useState(false);
|
518 |
const [highlighter, setHighlighter] = useState<any>(null);
|
519 |
+
const theme = useStore(themeStore);
|
520 |
|
521 |
const toggleFullscreen = useCallback(() => {
|
522 |
setIsFullscreen(prev => !prev);
|
|
|
526 |
|
527 |
useEffect(() => {
|
528 |
getHighlighter({
|
529 |
+
themes: ['github-dark', 'github-light'],
|
530 |
langs: ['typescript', 'javascript', 'json', 'html', 'css', 'jsx', 'tsx']
|
531 |
}).then(setHighlighter);
|
532 |
}, []);
|
|
|
556 |
highlighter={highlighter}
|
557 |
language={language}
|
558 |
block={block}
|
559 |
+
theme={theme}
|
560 |
/>
|
561 |
))}
|
562 |
</div>
|
|
|
565 |
beforeCode={beforeCode}
|
566 |
language={language}
|
567 |
highlighter={highlighter}
|
568 |
+
theme={theme}
|
569 |
/>
|
570 |
)}
|
571 |
</div>
|