From cd71c494da5871dbcf34e75db31a0956c4d91c42 Mon Sep 17 00:00:00 2001 From: AlI <13054619+411A@users.noreply.github.com> Date: Sun, 19 Oct 2025 18:33:59 +0330 Subject: [PATCH] feat(settings): add custom highlight color picker (#2273) Add ability to customize highlight colors with hex color picker. Users can now set custom colors for all five highlight styles (red, violet, blue, green, yellow) in the settings panel. Fixes #2271 --- .../reader/components/annotator/Annotator.tsx | 3 +- .../components/annotator/HighlightOptions.tsx | 50 +++++++++++-------- .../src/components/settings/ColorInput.tsx | 42 +++++++++++++++- .../src/components/settings/ColorPanel.tsx | 39 +++++++++++++++ apps/readest-app/src/services/constants.ts | 9 ++++ apps/readest-app/src/types/settings.ts | 1 + 6 files changed, 121 insertions(+), 23 deletions(-) diff --git a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx index 722a2f5a..30dd48ce 100644 --- a/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/Annotator.tsx @@ -142,7 +142,8 @@ const Annotator: React.FC<{ bookKey: string }> = ({ bookKey }) => { const detail = (event as CustomEvent).detail; const { draw, annotation, doc, range } = detail; const { style, color } = annotation as BookNote; - const hexColor = color ? HIGHLIGHT_COLOR_HEX[color] : color; + const customColors = settings.globalReadSettings.customHighlightColors; + const hexColor = color && customColors ? customColors[color] : color ? HIGHLIGHT_COLOR_HEX[color] : color; if (style === 'highlight') { draw(Overlayer.highlight, { color: hexColor }); } else if (['underline', 'squiggly'].includes(style as string)) { diff --git a/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx b/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx index b0963a46..f3c8e13f 100644 --- a/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx +++ b/apps/readest-app/src/app/reader/components/annotator/HighlightOptions.tsx @@ -4,6 +4,7 @@ import { FaCheckCircle } from 'react-icons/fa'; import { HighlightColor, HighlightStyle } from '@/types/book'; import { useSettingsStore } from '@/store/settingsStore'; import { useResponsiveSize } from '@/hooks/useResponsiveSize'; +import { DEFAULT_CUSTOM_HIGHLIGHT_COLORS } from '@/services/constants'; const styles = ['highlight', 'underline', 'squiggly'] as HighlightStyle[]; const colors = ['red', 'violet', 'blue', 'green', 'yellow'] as HighlightColor[]; @@ -25,6 +26,7 @@ const HighlightOptions: React.FC = ({ }) => { const { settings, setSettings } = useSettingsStore(); const globalReadSettings = settings.globalReadSettings; + const customColors = globalReadSettings.customHighlightColors || DEFAULT_CUSTOM_HIGHLIGHT_COLORS; const [selectedStyle, setSelectedStyle] = React.useState(_selectedStyle); const [selectedColor, setSelectedColor] = React.useState(_selectedColor); const size16 = useResponsiveSize(16); @@ -65,24 +67,26 @@ const HighlightOptions: React.FC = ({ style={{ width: size28, height: size28, minHeight: size28 }} >
A
@@ -101,11 +105,15 @@ const HighlightOptions: React.FC = ({ ))} diff --git a/apps/readest-app/src/components/settings/ColorInput.tsx b/apps/readest-app/src/components/settings/ColorInput.tsx index bb3a14a0..d863204a 100644 --- a/apps/readest-app/src/components/settings/ColorInput.tsx +++ b/apps/readest-app/src/components/settings/ColorInput.tsx @@ -5,9 +5,11 @@ type ColorInputProps = { label: string; value: string; onChange: (value: string) => void; + compact?: boolean; + pickerPosition?: 'left' | 'center' | 'right'; }; -const ColorInput: React.FC = ({ label, value, onChange }) => { +const ColorInput: React.FC = ({ label, value, onChange, compact = false, pickerPosition = 'left' }) => { const [isOpen, setIsOpen] = useState(false); const pickerRef = useRef(null); @@ -29,6 +31,44 @@ const ColorInput: React.FC = ({ label, value, onChange }) => { onChange(colorResult.hex); }; + const getPickerPositionClass = () => { + if (pickerPosition === 'right') { + return 'right-0'; + } else if (pickerPosition === 'center') { + return 'left-1/2 -translate-x-1/2'; + } + return 'left-0'; + }; + + if (compact) { + return ( +
+ onChange(e.target.value)} + onClick={() => setIsOpen(!isOpen)} + className='bg-base-100 text-base-content border-base-200/75 w-16 cursor-pointer rounded border px-1 py-0.5 text-center font-mono text-xs' + /> + + {isOpen && ( +
+ +
+ )} +
+ ); + } + return (
diff --git a/apps/readest-app/src/components/settings/ColorPanel.tsx b/apps/readest-app/src/components/settings/ColorPanel.tsx index 59f6d108..3937b819 100644 --- a/apps/readest-app/src/components/settings/ColorPanel.tsx +++ b/apps/readest-app/src/components/settings/ColorPanel.tsx @@ -27,6 +27,9 @@ import { useFileSelector } from '@/hooks/useFileSelector'; import { PREDEFINED_TEXTURES } from '@/styles/textures'; import Select from '@/components/Select'; import ThemeEditor from './ThemeEditor'; +import ColorInput from './ColorInput'; +import { HighlightColor } from '@/types/book'; +import { DEFAULT_CUSTOM_HIGHLIGHT_COLORS } from '@/services/constants'; const ColorPanel: React.FC = ({ bookKey, onRegisterReset }) => { const _ = useTranslation(); @@ -53,6 +56,10 @@ const ColorPanel: React.FC = ({ bookKey, onRegisterReset const [backgroundOpacity, setBackgroundOpacity] = useState(viewSettings.backgroundOpacity); const [backgroundSize, setBackgroundSize] = useState(viewSettings.backgroundSize); + const [customHighlightColors, setCustomHighlightColors] = useState>( + settings.globalReadSettings.customHighlightColors || DEFAULT_CUSTOM_HIGHLIGHT_COLORS, + ); + const { textures: customTextures, addTexture, @@ -78,6 +85,7 @@ const ColorPanel: React.FC = ({ bookKey, onRegisterReset setSelectedTextureId('none'); setBackgroundOpacity(0.4); setBackgroundSize('2048px'); + setCustomHighlightColors(DEFAULT_CUSTOM_HIGHLIGHT_COLORS); }; useEffect(() => { @@ -433,6 +441,37 @@ const ColorPanel: React.FC = ({ bookKey, onRegisterReset )}
+
+

{_('Custom Highlight Colors')}

+
+
+ {(['red', 'violet', 'blue', 'green', 'yellow'] as HighlightColor[]).map((color, index, array) => { + const position = index === 0 ? 'left' : index === array.length - 1 ? 'right' : 'center'; + return ( +
+
+ { + customHighlightColors[color] = value; + setCustomHighlightColors({ ...customHighlightColors }); + settings.globalReadSettings.customHighlightColors = customHighlightColors; + setSettings(settings); + }} + /> +
+ ); + })} +
+
+
+

{_('Code Highlighting')}

diff --git a/apps/readest-app/src/services/constants.ts b/apps/readest-app/src/services/constants.ts index 5c9ed650..42ed6915 100644 --- a/apps/readest-app/src/services/constants.ts +++ b/apps/readest-app/src/services/constants.ts @@ -78,6 +78,14 @@ export const DEFAULT_SYSTEM_SETTINGS: Partial = { lastSyncedAtNotes: 0, }; +export const DEFAULT_CUSTOM_HIGHLIGHT_COLORS: Record = { + red: '#fca5a5', // red-300 + yellow: '#fde047', // yellow-300 + green: '#86efac', // green-300 + blue: '#93c5fd', // blue-300 + violet: '#c4b5fd', // violet-300 +}; + export const DEFAULT_READSETTINGS: ReadSettings = { sideBarWidth: '15%', isSideBarPinned: true, @@ -94,6 +102,7 @@ export const DEFAULT_READSETTINGS: ReadSettings = { underline: 'green', squiggly: 'blue', }, + customHighlightColors: DEFAULT_CUSTOM_HIGHLIGHT_COLORS, }; export const DEFAULT_MOBILE_READSETTINGS: Partial = { diff --git a/apps/readest-app/src/types/settings.ts b/apps/readest-app/src/types/settings.ts index d217889c..34c600e0 100644 --- a/apps/readest-app/src/types/settings.ts +++ b/apps/readest-app/src/types/settings.ts @@ -22,6 +22,7 @@ export interface ReadSettings { highlightStyle: HighlightStyle; highlightStyles: Record; + customHighlightColors: Record; customThemes: CustomTheme[]; }