mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 17:21:05 +00:00
fix: add missing parseCssColor and verboseLog modules for calendar sync build
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
import {
|
||||||
|
extractSolidColor,
|
||||||
|
normalizeCssColorString,
|
||||||
|
parseCssColor,
|
||||||
|
} from "./parseCssColor";
|
||||||
|
|
||||||
|
describe("normalizeCssColorString", () => {
|
||||||
|
it("lowercases uppercase RGBA/RGB function names", () => {
|
||||||
|
expect(normalizeCssColorString("RGBA(3, 29, 11, 0.58)")).toBe(
|
||||||
|
"rgba(3, 29, 11, 0.58)",
|
||||||
|
);
|
||||||
|
expect(normalizeCssColorString("RGB(10, 20, 30)")).toBe("rgb(10, 20, 30)");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("extractSolidColor", () => {
|
||||||
|
it("extracts solid uppercase RGBA values", () => {
|
||||||
|
expect(extractSolidColor("RGBA(3, 29, 11, 0.58)")).toBe(
|
||||||
|
"rgba(3, 29, 11, 0.58)",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("extracts the first rgba stop from gradients with mixed casing", () => {
|
||||||
|
expect(
|
||||||
|
extractSolidColor(
|
||||||
|
"linear-gradient(40deg, rgba(201,61,0,1) 0%, RGBA(170, 5, 58, 1) 100%)",
|
||||||
|
),
|
||||||
|
).toBe("rgba(201,61,0,1)");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("parseCssColor", () => {
|
||||||
|
it("parses uppercase RGBA without throwing", () => {
|
||||||
|
const parsed = parseCssColor("RGBA(3, 29, 11, 0.58)");
|
||||||
|
expect(parsed.alpha()).toBeCloseTo(0.58, 2);
|
||||||
|
expect(parsed.red()).toBe(3);
|
||||||
|
expect(parsed.green()).toBe(29);
|
||||||
|
expect(parsed.blue()).toBe(11);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back when the value is not a colour", () => {
|
||||||
|
expect(parseCssColor("not-a-color", "#007bff").hex().toLowerCase()).toBe(
|
||||||
|
"#007bff",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import Color from "color";
|
||||||
|
|
||||||
|
type ColorInstance = ReturnType<typeof Color>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SEQTA themes and user gradients often use uppercase `RGBA()` / `RGB()`.
|
||||||
|
* The `color` package only accepts lowercase function names.
|
||||||
|
*/
|
||||||
|
export function normalizeCssColorString(value: string): string {
|
||||||
|
return value
|
||||||
|
.trim()
|
||||||
|
.replace(/\bRGBA?\(/gi, (match) => match.toLowerCase())
|
||||||
|
.replace(/\bHSLA?\(/gi, (match) => match.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pick a single solid colour from a CSS value (hex, rgb(a), hsl(a), or gradient). */
|
||||||
|
export function extractSolidColor(value: string): string | null {
|
||||||
|
const trimmed = normalizeCssColorString(value);
|
||||||
|
if (!trimmed || trimmed === "initial") return null;
|
||||||
|
if (
|
||||||
|
trimmed.startsWith("#") ||
|
||||||
|
/^rgba?\(/i.test(trimmed) ||
|
||||||
|
/^hsla?\(/i.test(trimmed)
|
||||||
|
) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
if (trimmed.includes("gradient")) {
|
||||||
|
const match = trimmed.match(
|
||||||
|
/#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{3}|rgba?\([^)]+\)/gi,
|
||||||
|
);
|
||||||
|
return match?.[0] ? normalizeCssColorString(match[0]) : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Parse a CSS colour for the `color` library; never throws. */
|
||||||
|
export function parseCssColor(value: string, fallback = "#007bff"): ColorInstance {
|
||||||
|
const candidates = [
|
||||||
|
extractSolidColor(value),
|
||||||
|
normalizeCssColorString(value),
|
||||||
|
].filter((candidate): candidate is string => Boolean(candidate));
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
try {
|
||||||
|
return Color(candidate);
|
||||||
|
} catch {
|
||||||
|
// try next strategy
|
||||||
|
}
|
||||||
|
|
||||||
|
const rgbaMatch = candidate.match(
|
||||||
|
/rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)/i,
|
||||||
|
);
|
||||||
|
if (rgbaMatch) {
|
||||||
|
try {
|
||||||
|
const [, r, g, b, a] = rgbaMatch;
|
||||||
|
const rgb = Color.rgb(Number(r), Number(g), Number(b));
|
||||||
|
return a !== undefined ? rgb.alpha(Number(a)) : rgb;
|
||||||
|
} catch {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Color(fallback);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { settingsState } from "@/seqta/utils/listeners/SettingsState";
|
||||||
|
|
||||||
|
const VERBOSE_LOG_ATTR = "data-bsplus-verbose-log";
|
||||||
|
|
||||||
|
export function isVerboseLoggingEnabled(): boolean {
|
||||||
|
return Boolean(settingsState.devMode && settingsState.verboseLogging);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function syncVerboseLogDomFlag(): void {
|
||||||
|
if (typeof document === "undefined") return;
|
||||||
|
document.documentElement.toggleAttribute(
|
||||||
|
VERBOSE_LOG_ATTR,
|
||||||
|
isVerboseLoggingEnabled(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let initialized = false;
|
||||||
|
|
||||||
|
/** Register DOM flag sync when dev / verbose toggles change. Call after settings load. */
|
||||||
|
export function initVerboseLogging(): void {
|
||||||
|
if (initialized) return;
|
||||||
|
initialized = true;
|
||||||
|
syncVerboseLogDomFlag();
|
||||||
|
settingsState.register("devMode", () => syncVerboseLogDomFlag());
|
||||||
|
settingsState.register("verboseLogging", () => syncVerboseLogDomFlag());
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verboseDebug(...args: unknown[]): void {
|
||||||
|
if (isVerboseLoggingEnabled()) console.debug(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verboseInfo(...args: unknown[]): void {
|
||||||
|
if (isVerboseLoggingEnabled()) console.info(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verboseLog(...args: unknown[]): void {
|
||||||
|
if (isVerboseLoggingEnabled()) console.log(...args);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user