mirror of
https://github.com/vadimmelnicuk/meo.git
synced 2026-09-26 18:05:36 +00:00
Merge pull request #73 from apoapostolov/feat/active-line-highlight-control
feat: Customize active-line background through themes
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
# Markdown Editor Optimized (MEO)
|
||||
---
|
||||
## Unreleased
|
||||
- Added the theme color `activeLineBackground` to customize the active-line highlight in Live and Source
|
||||
- Added a shared Read Only toggle beside Spell Check that blocks editing in Live and Source while preserving reading, selection, copying, and external updates; the preference defaults to off and persists across documents, sessions, and workspaces
|
||||
- Added visual multiline editing to live table cells while storing line breaks as `<br>` in Markdown
|
||||
- Added link-reference definitions as visible linked lists in HTML and PDF exports, and restored copying selections across rendered blocks
|
||||
|
||||
@@ -57,6 +57,16 @@ Guide on how to customise the editor background, colors, syntax highlighting, fo
|
||||
- Exported HTML/PDF documents use this background color.
|
||||
- Color values must be valid `#hex`, `rgb()/rgba()`, `hsl()/hsla()`, or `var(--...)`.
|
||||
|
||||
## activeLineBackground
|
||||
|
||||
`activeLineBackground` controls the background highlight on the line that currently has the cursor (Live and Source modes).
|
||||
|
||||
- Optional. Leave empty (or omit) to use the built-in mix derived from `base03`.
|
||||
- Set to a color (`#hex`, `rgb()`, `hsl()`, `var(--...)`) to restyle the highlight for your theme.
|
||||
- Set to `transparent` to remove the active-line wash via the theme (without turning off CodeMirror's active-line machinery globally).
|
||||
|
||||
Read Only hides the active-line highlight regardless of this color.
|
||||
|
||||
## colors
|
||||
|
||||
`colors` is the 9-color base palette (`base01` to `base09`) that drives the theme.
|
||||
|
||||
+8
-2
@@ -354,13 +354,14 @@
|
||||
"h6FontWeight": "600",
|
||||
"liveLineHeight": 1.5,
|
||||
"sourceLineHeight": 1.5
|
||||
}
|
||||
},
|
||||
"activeLineBackground": ""
|
||||
},
|
||||
"defaultSnippets": [
|
||||
{
|
||||
"label": "One Monokai theme payload",
|
||||
"description": "Insert the full theme object with sensible defaults.",
|
||||
"body": "{\n\t\"id\": \"one-monokai\",\n\t\"name\": \"One Monokai\",\n\t\"backgroundColor\": \"var(--vscode-editor-background)\",\n\t\"colors\": {\n\t\t\"base01\": \"var(--vscode-editor-foreground)\",\n\t\t\"base02\": \"#676f7d\",\n\t\t\"base03\": \"#3e444d\",\n\t\t\"base04\": \"#e06c75\",\n\t\t\"base05\": \"#61afef\",\n\t\t\"base06\": \"#66d9ef\",\n\t\t\"base07\": \"#e5c07b\",\n\t\t\"base08\": \"#c678dd\",\n\t\t\"base09\": \"#98c379\"\n\t},\n\t\"syntaxTokens\": {},\n\t\"fonts\": {\n\t\t\"liveFont\": \"\",\n\t\t\"sourceFont\": \"\",\n\t\t\"liveFontWeight\": \"\",\n\t\t\"sourceFontWeight\": \"\",\n\t\t\"liveFontSize\": null,\n\t\t\"sourceFontSize\": null,\n\t\t\"h1FontSize\": 1.6,\n\t\t\"h2FontSize\": 1.5,\n\t\t\"h3FontSize\": 1.3,\n\t\t\"h4FontSize\": 1.2,\n\t\t\"h5FontSize\": 1.1,\n\t\t\"h6FontSize\": 1,\n\t\t\"h1FontWeight\": \"600\",\n\t\t\"h2FontWeight\": \"600\",\n\t\t\"h3FontWeight\": \"600\",\n\t\t\"h4FontWeight\": \"600\",\n\t\t\"h5FontWeight\": \"600\",\n\t\t\"h6FontWeight\": \"600\",\n\t\t\"liveLineHeight\": 1.5,\n\t\t\"sourceLineHeight\": 1.5\n\t}\n}"
|
||||
"body": "{\n\t\"id\": \"one-monokai\",\n\t\"name\": \"One Monokai\",\n\t\"backgroundColor\": \"var(--vscode-editor-background)\",\n\t\"activeLineBackground\": \"\",\n\t\"colors\": {\n\t\t\"base01\": \"var(--vscode-editor-foreground)\",\n\t\t\"base02\": \"#676f7d\",\n\t\t\"base03\": \"#3e444d\",\n\t\t\"base04\": \"#e06c75\",\n\t\t\"base05\": \"#61afef\",\n\t\t\"base06\": \"#66d9ef\",\n\t\t\"base07\": \"#e5c07b\",\n\t\t\"base08\": \"#c678dd\",\n\t\t\"base09\": \"#98c379\"\n\t},\n\t\"syntaxTokens\": {},\n\t\"fonts\": {\n\t\t\"liveFont\": \"\",\n\t\t\"sourceFont\": \"\",\n\t\t\"liveFontWeight\": \"\",\n\t\t\"sourceFontWeight\": \"\",\n\t\t\"liveFontSize\": null,\n\t\t\"sourceFontSize\": null,\n\t\t\"h1FontSize\": 1.6,\n\t\t\"h2FontSize\": 1.5,\n\t\t\"h3FontSize\": 1.3,\n\t\t\"h4FontSize\": 1.2,\n\t\t\"h5FontSize\": 1.1,\n\t\t\"h6FontSize\": 1,\n\t\t\"h1FontWeight\": \"600\",\n\t\t\"h2FontWeight\": \"600\",\n\t\t\"h3FontWeight\": \"600\",\n\t\t\"h4FontWeight\": \"600\",\n\t\t\"h5FontWeight\": \"600\",\n\t\t\"h6FontWeight\": \"600\",\n\t\t\"liveLineHeight\": 1.5,\n\t\t\"sourceLineHeight\": 1.5\n\t}\n}"
|
||||
}
|
||||
],
|
||||
"description": "Theme payload containing the editor background, base palette colors, optional token overrides, and font settings.",
|
||||
@@ -765,6 +766,11 @@
|
||||
"sourceLineHeight"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"activeLineBackground": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Active-line background in Live and Source. Empty uses the default color; accepts theme colors or transparent. Read Only hides the highlight."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -335,6 +335,8 @@ export type ThemeSettings = {
|
||||
id: string;
|
||||
name: string;
|
||||
backgroundColor: string;
|
||||
/** Empty uses the built-in mix from base03. */
|
||||
activeLineBackground: string;
|
||||
colors: ThemeColors;
|
||||
syntaxTokens: ThemeSyntaxTokens;
|
||||
fonts: ThemeFonts;
|
||||
@@ -405,6 +407,7 @@ const createThemeFromColors = (params: {
|
||||
id: string;
|
||||
name: string;
|
||||
backgroundColor?: string;
|
||||
activeLineBackground?: string;
|
||||
colors?: Partial<ThemeColors>;
|
||||
syntaxTokenPaletteOverrides?: Partial<ThemeSyntaxTokenPalette>;
|
||||
syntaxTokenOverrides?: Partial<ThemeSyntaxTokens>;
|
||||
@@ -416,6 +419,7 @@ const createThemeFromColors = (params: {
|
||||
id: params.id,
|
||||
name: params.name,
|
||||
backgroundColor: params.backgroundColor ?? defaultThemeBackgroundColor,
|
||||
activeLineBackground: params.activeLineBackground ?? '',
|
||||
colors,
|
||||
syntaxTokens: {
|
||||
...buildSyntaxTokenColors(colors, params.syntaxTokenPaletteOverrides),
|
||||
@@ -576,7 +580,7 @@ export const defaultThemeSettings: ThemeSettings = themePresets[0] as ThemeSetti
|
||||
|
||||
const hexColorRegex = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
||||
const rgbColorRegex = /^rgba?\(\s*(?:\d{1,3}\s*,\s*){2}\d{1,3}(?:\s*,\s*(?:0(?:\.\d+)?|1(?:\.0+)?|\d*\.?\d+))?\s*\)$/;
|
||||
const hslColorRegex = /^hsla?\(\s*(?:[+\-]?\d+(?:\.\d+)?(?:deg|rad|grad|turn)?\s*,\s*){2}\d{1,3}%?(?:\s*,\s*(?:0(?:\.\d+)?|1(?:\.0+)?|\d*\.?\d+))?\s*\)$/;
|
||||
const hslColorRegex = /^hsla?\(\s*[+\-]?\d+(?:\.\d+)?(?:deg|rad|grad|turn)?\s*,\s*\d+(?:\.\d+)?%\s*,\s*\d+(?:\.\d+)?%(?:\s*,\s*(?:0(?:\.\d+)?|1(?:\.0+)?|\d*\.?\d+))?\s*\)$/;
|
||||
const cssVarColorRegex = /^var\(\s*--[A-Za-z0-9_-]+\s*(?:,\s*[^)]+)?\)$/;
|
||||
|
||||
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
||||
@@ -599,6 +603,22 @@ const isValidThemeColor = (value: string): boolean => {
|
||||
return hexColorRegex.test(candidate) || rgbColorRegex.test(candidate) || hslColorRegex.test(candidate) || cssVarColorRegex.test(candidate);
|
||||
};
|
||||
|
||||
const isValidActiveLineColor = (value: string): boolean => (
|
||||
value.trim().toLowerCase() === 'transparent' || isValidThemeColor(value)
|
||||
);
|
||||
|
||||
/** Empty string uses the built-in mix from base03. */
|
||||
const resolveOptionalThemeColor = (value: unknown): string => {
|
||||
if (typeof value !== 'string') {
|
||||
return '';
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return '';
|
||||
}
|
||||
return isValidActiveLineColor(trimmed) ? trimmed : '';
|
||||
};
|
||||
|
||||
const sanitizeThemeColor = (value: unknown, fallback: string): string => {
|
||||
if (typeof value !== 'string') {
|
||||
return fallback;
|
||||
@@ -746,6 +766,7 @@ export const serializeThemeSettings = (theme: ThemeSettings): ThemeSettingsPaylo
|
||||
id: theme.id,
|
||||
name: theme.name,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
activeLineBackground: theme.activeLineBackground,
|
||||
colors: { ...theme.colors },
|
||||
syntaxTokens,
|
||||
fonts: { ...theme.fonts }
|
||||
@@ -758,6 +779,7 @@ export const resolveTheme = (themeOverride?: Partial<ThemeSettings>): ThemeSetti
|
||||
id: normalizeString(themeOverride?.id, defaultThemeSettings.id),
|
||||
name: normalizeString(themeOverride?.name, defaultThemeSettings.name),
|
||||
backgroundColor: sanitizeThemeColor(themeOverride?.backgroundColor, defaultThemeBackgroundColor),
|
||||
activeLineBackground: resolveOptionalThemeColor(themeOverride?.activeLineBackground),
|
||||
colors,
|
||||
syntaxTokens: resolveThemeSyntaxTokens(themeOverride?.syntaxTokens, colors),
|
||||
fonts: resolveThemeFonts(themeOverride?.fonts)
|
||||
@@ -793,6 +815,14 @@ export const validateThemePayload = (value: unknown): ThemeValidationResult => {
|
||||
}
|
||||
}
|
||||
|
||||
if (value.activeLineBackground !== undefined) {
|
||||
if (typeof value.activeLineBackground !== 'string') {
|
||||
errors.push('Theme "activeLineBackground" must be a string.');
|
||||
} else if (value.activeLineBackground.trim() && !isValidActiveLineColor(value.activeLineBackground)) {
|
||||
errors.push('Theme "activeLineBackground" must be empty (default), transparent, or a valid hex, rgb, hsl, or var(--...) color string.');
|
||||
}
|
||||
}
|
||||
|
||||
const rawColors = value.colors;
|
||||
if (!isRecord(rawColors)) {
|
||||
errors.push('Theme "colors" must be an object.');
|
||||
|
||||
@@ -123,9 +123,13 @@ export const applyThemeSettings = (theme?: ThemeSettings): void => {
|
||||
`color-mix(in srgb, ${resolvedTheme.colors.base05} 28%, transparent 72%)`
|
||||
);
|
||||
rootStyle.setProperty('--meo-caret-color', resolvedTheme.colors.base01);
|
||||
const activeLineBackground = typeof resolvedTheme.activeLineBackground === 'string'
|
||||
? resolvedTheme.activeLineBackground.trim()
|
||||
: '';
|
||||
rootStyle.setProperty(
|
||||
'--meo-active-line-bg',
|
||||
`color-mix(in srgb, ${resolvedTheme.colors.base03} 35%, transparent 65%)`
|
||||
activeLineBackground
|
||||
|| `color-mix(in srgb, ${resolvedTheme.colors.base03} 35%, transparent 65%)`
|
||||
);
|
||||
|
||||
for (const key of themeColorKeys) {
|
||||
|
||||
Vendored
+1
@@ -70,6 +70,7 @@ interface ThemeSettings {
|
||||
id: string;
|
||||
name: string;
|
||||
backgroundColor?: string;
|
||||
activeLineBackground?: string;
|
||||
colors: Record<string, string>;
|
||||
syntaxTokens: Record<string, string>;
|
||||
fonts: {
|
||||
|
||||
Reference in New Issue
Block a user