From e4b6d1a12278b201bf32bef3e17b982ca56de0bf Mon Sep 17 00:00:00 2001 From: Gigi Date: Sun, 5 Oct 2025 04:12:31 +0100 Subject: [PATCH] feat: add configurable highlight colors - Add highlightColor setting with 6 preset colors (yellow, orange, pink, green, blue, purple) - Implement color picker UI with square color swatches - Use CSS variables to dynamically apply highlight colors - Add hex to RGB conversion for color transparency support - Update both marker and underline styles to use selected color --- src/components/Bookmarks.tsx | 1 + src/components/ContentPanel.tsx | 14 ++++++- src/components/Settings.tsx | 36 +++++++++++++++- src/index.css | 73 +++++++++++++++++++++++++-------- src/services/settingsService.ts | 1 + 5 files changed, 106 insertions(+), 19 deletions(-) diff --git a/src/components/Bookmarks.tsx b/src/components/Bookmarks.tsx index eb9d7635..b0ef7b57 100644 --- a/src/components/Bookmarks.tsx +++ b/src/components/Bookmarks.tsx @@ -131,6 +131,7 @@ const Bookmarks: React.FC = ({ relayPool, onLogout }) => { highlights={highlights} showUnderlines={showUnderlines} highlightStyle={settings.highlightStyle || 'marker'} + highlightColor={settings.highlightColor || '#ffff00'} onHighlightClick={(id) => { setSelectedHighlightId(id) if (isHighlightsCollapsed) setIsHighlightsCollapsed(false) diff --git a/src/components/ContentPanel.tsx b/src/components/ContentPanel.tsx index 59a996d8..d177fc0d 100644 --- a/src/components/ContentPanel.tsx +++ b/src/components/ContentPanel.tsx @@ -8,6 +8,14 @@ import { applyHighlightsToHTML } from '../utils/highlightMatching' import { readingTime } from 'reading-time-estimator' import { filterHighlightsByUrl } from '../utils/urlHelpers' +// Helper to convert hex color to RGB values +function hexToRgb(hex: string): string { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) + return result + ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}` + : '255, 255, 0' +} + interface ContentPanelProps { loading: boolean title?: string @@ -17,6 +25,7 @@ interface ContentPanelProps { highlights?: Highlight[] showUnderlines?: boolean highlightStyle?: 'marker' | 'underline' + highlightColor?: string onHighlightClick?: (highlightId: string) => void selectedHighlightId?: string } @@ -30,6 +39,7 @@ const ContentPanel: React.FC = ({ highlights = [], showUnderlines = true, highlightStyle = 'marker', + highlightColor = '#ffff00', onHighlightClick, selectedHighlightId }) => { @@ -151,8 +161,10 @@ const ContentPanel: React.FC = ({ ) } + const highlightRgb = hexToRgb(highlightColor) + return ( -
+
{title && (

{title}

diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx index 24f5e6ce..32620a6e 100644 --- a/src/components/Settings.tsx +++ b/src/components/Settings.tsx @@ -4,6 +4,14 @@ import { UserSettings } from '../services/settingsService' import IconButton from './IconButton' import { loadFont, getFontFamily } from '../utils/fontLoader' +// Helper to convert hex color to RGB values +function hexToRgb(hex: string): string { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) + return result + ? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}` + : '255, 255, 0' +} + interface SettingsProps { settings: UserSettings onSave: (settings: UserSettings) => Promise @@ -131,14 +139,38 @@ const Settings: React.FC = ({ settings, onSave, onClose }) => {
+
+ +
+ {[ + { name: 'Yellow', value: '#ffff00' }, + { name: 'Orange', value: '#ff9500' }, + { name: 'Pink', value: '#ff69b4' }, + { name: 'Green', value: '#00ff7f' }, + { name: 'Blue', value: '#4da6ff' }, + { name: 'Purple', value: '#b19cd9' } + ].map(color => ( +
+
+
Preview

The Quick Brown Fox

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.

diff --git a/src/index.css b/src/index.css index 35f49bba..f13d9fdf 100644 --- a/src/index.css +++ b/src/index.css @@ -1404,36 +1404,38 @@ body { /* Inline content highlights - fluorescent marker style */ .content-highlight, .content-highlight-marker { - background: rgba(255, 255, 0, 0.35); + --highlight-rgb: 255, 255, 0; + background: rgba(var(--highlight-rgb), 0.35); padding: 0.125rem 0.25rem; cursor: pointer; transition: all 0.2s ease; position: relative; border-radius: 2px; - box-shadow: 0 0 8px rgba(255, 255, 0, 0.2); + box-shadow: 0 0 8px rgba(var(--highlight-rgb), 0.2); } .content-highlight:hover, .content-highlight-marker:hover { - background: rgba(255, 255, 0, 0.5); - box-shadow: 0 0 12px rgba(255, 255, 0, 0.3); + background: rgba(var(--highlight-rgb), 0.5); + box-shadow: 0 0 12px rgba(var(--highlight-rgb), 0.3); } /* Underline style for highlights */ .content-highlight-underline { + --highlight-rgb: 255, 255, 0; background: transparent; padding: 0; cursor: pointer; transition: all 0.2s ease; position: relative; text-decoration: underline; - text-decoration-color: rgba(255, 200, 0, 0.8); + text-decoration-color: rgba(var(--highlight-rgb), 0.8); text-decoration-thickness: 2px; text-underline-offset: 2px; } .content-highlight-underline:hover { - text-decoration-color: rgba(255, 200, 0, 1); + text-decoration-color: rgba(var(--highlight-rgb), 1); text-decoration-thickness: 3px; } @@ -1445,19 +1447,19 @@ body { @keyframes highlight-pulse-animation { 0%, 100% { - box-shadow: 0 0 8px rgba(255, 255, 0, 0.2); + box-shadow: 0 0 8px rgba(var(--highlight-rgb, 255, 255, 0), 0.2); transform: scale(1); } 25% { - box-shadow: 0 0 20px rgba(255, 255, 0, 0.6); + box-shadow: 0 0 20px rgba(var(--highlight-rgb, 255, 255, 0), 0.6); transform: scale(1.02); } 50% { - box-shadow: 0 0 8px rgba(255, 255, 0, 0.2); + box-shadow: 0 0 8px rgba(var(--highlight-rgb, 255, 255, 0), 0.2); transform: scale(1); } 75% { - box-shadow: 0 0 20px rgba(255, 255, 0, 0.6); + box-shadow: 0 0 20px rgba(var(--highlight-rgb, 255, 255, 0), 0.6); transform: scale(1.02); } } @@ -1482,22 +1484,22 @@ body { @media (prefers-color-scheme: light) { .content-highlight, .content-highlight-marker { - background: rgba(255, 255, 0, 0.4); - box-shadow: 0 0 6px rgba(255, 255, 0, 0.15); + background: rgba(var(--highlight-rgb), 0.4); + box-shadow: 0 0 6px rgba(var(--highlight-rgb), 0.15); } .content-highlight:hover, .content-highlight-marker:hover { - background: rgba(255, 255, 0, 0.55); - box-shadow: 0 0 10px rgba(255, 255, 0, 0.25); + background: rgba(var(--highlight-rgb), 0.55); + box-shadow: 0 0 10px rgba(var(--highlight-rgb), 0.25); } .content-highlight-underline { - text-decoration-color: rgba(255, 180, 0, 0.9); + text-decoration-color: rgba(var(--highlight-rgb), 0.9); } .content-highlight-underline:hover { - text-decoration-color: rgba(255, 180, 0, 1); + text-decoration-color: rgba(var(--highlight-rgb), 1); } .highlight-indicator { @@ -1587,6 +1589,45 @@ body { gap: 0.5rem; } +.color-picker { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.color-swatch { + width: 2.5rem; + height: 2.5rem; + border: 2px solid #444; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s; + position: relative; +} + +.color-swatch:hover { + border-color: #888; + transform: scale(1.05); +} + +.color-swatch.active { + border-color: #fff; + box-shadow: 0 0 0 2px #646cff; + transform: scale(1.1); +} + +.color-swatch.active::after { + content: '✓'; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: #000; + font-size: 1rem; + font-weight: bold; + text-shadow: 0 0 2px #fff; +} + .font-size-btn { min-width: 2.5rem; height: 2.5rem; diff --git a/src/services/settingsService.ts b/src/services/settingsService.ts index d4bea1ee..3e7c1317 100644 --- a/src/services/settingsService.ts +++ b/src/services/settingsService.ts @@ -17,6 +17,7 @@ export interface UserSettings { readingFont?: string fontSize?: number highlightStyle?: 'marker' | 'underline' + highlightColor?: string } export async function loadSettings(