mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(app): treat a black instance theme colour as unset (#3502)
This commit is contained in:
1 parent
41da6b5985
commit
b9ea837c75
5 files changed
+34
-20
No files matched your search
@@ -57,6 +57,7 @@ import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
|
|||||||
import {resolveSmtpTlsMode, type SmtpTlsMode, smtpTlsModeFromSecure} from '@fluxer/constants/src/SmtpConstants';
|
import {resolveSmtpTlsMode, type SmtpTlsMode, smtpTlsModeFromSecure} from '@fluxer/constants/src/SmtpConstants';
|
||||||
import {ConflictError} from '@fluxer/errors/src/domains/core/ConflictError';
|
import {ConflictError} from '@fluxer/errors/src/domains/core/ConflictError';
|
||||||
import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError';
|
import {ServiceUnavailableError} from '@fluxer/errors/src/domains/core/ServiceUnavailableError';
|
||||||
|
import {normalizeThemeColor} from '@fluxer/instance_bootstrap/src/ThemeColor';
|
||||||
import {
|
import {
|
||||||
InstanceConfigResponse,
|
InstanceConfigResponse,
|
||||||
InstanceConfigUpdateRequest,
|
InstanceConfigUpdateRequest,
|
||||||
@@ -414,14 +415,6 @@ function normalizeOptionalPublicString(value: string | null | undefined, fallbac
|
|||||||
return value === undefined ? fallback : normalizeOptionalString(value);
|
return value === undefined ? fallback : normalizeOptionalString(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeThemeColor(value: unknown): string | null {
|
|
||||||
const match = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(normalizeOptionalString(value) ?? '');
|
|
||||||
if (match === null) return null;
|
|
||||||
const digits = match[1].toLowerCase();
|
|
||||||
const color = `#${digits.length === 3 ? digits.replace(/./g, '$&$&') : digits}`;
|
|
||||||
return color === '#000000' ? null : color;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDefaultPremiumProductName(): string {
|
function getDefaultPremiumProductName(): string {
|
||||||
return Config.instance.selfHosted ? 'Premium' : 'Plutonium';
|
return Config.instance.selfHosted ? 'Premium' : 'Plutonium';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,6 +270,21 @@ describe('runtimeSnapshotFromDiscovery', () => {
|
|||||||
).toThrow(/appPublic\.branding\.icon_url/u);
|
).toThrow(/appPublic\.branding\.icon_url/u);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('treats a black or malformed theme colour as unset and normalises the rest', () => {
|
||||||
|
const snapshot = runtimeSnapshotFromDiscovery(parsedDocument('t.test'));
|
||||||
|
const themeColorOf = (theme_color: unknown) =>
|
||||||
|
requireRuntimeConfigSnapshot({
|
||||||
|
...snapshot,
|
||||||
|
appPublic: {...snapshot.appPublic, branding: {...snapshot.appPublic.branding, theme_color}},
|
||||||
|
}).appPublic.branding.theme_color;
|
||||||
|
for (const unset of ['#000000', '#000', '000000', '', 'rgb(1, 2, 3)', 'rebeccapurple', 0, null]) {
|
||||||
|
expect(themeColorOf(unset)).toBeNull();
|
||||||
|
}
|
||||||
|
expect(themeColorOf('#FF0000')).toBe('#ff0000');
|
||||||
|
expect(themeColorOf(' f00 ')).toBe('#ff0000');
|
||||||
|
expect(themeColorOf('#000001')).toBe('#000001');
|
||||||
|
});
|
||||||
|
|
||||||
test('carries the advertised domain migration', () => {
|
test('carries the advertised domain migration', () => {
|
||||||
const domainMigration = {
|
const domainMigration = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
} from '@fluxer/instance_bootstrap/src/Discovery';
|
} from '@fluxer/instance_bootstrap/src/Discovery';
|
||||||
import {InstanceEndpointKind, normalizeInstanceEndpoint} from '@fluxer/instance_bootstrap/src/EndpointNormalization';
|
import {InstanceEndpointKind, normalizeInstanceEndpoint} from '@fluxer/instance_bootstrap/src/EndpointNormalization';
|
||||||
import {CanonicalNetworkProtocol} from '@fluxer/instance_bootstrap/src/NetworkOrigin';
|
import {CanonicalNetworkProtocol} from '@fluxer/instance_bootstrap/src/NetworkOrigin';
|
||||||
|
import {normalizeThemeColor} from '@fluxer/instance_bootstrap/src/ThemeColor';
|
||||||
import {
|
import {
|
||||||
AGE_POLICY_ACTIONS,
|
AGE_POLICY_ACTIONS,
|
||||||
type InstanceAgePolicy,
|
type InstanceAgePolicy,
|
||||||
@@ -413,7 +414,7 @@ function readAppPublic(value: unknown): InstanceAppPublic {
|
|||||||
logo_url: readNullableAbsoluteHttpUrl(branding, 'logo_url', 'appPublic.branding.logo_url'),
|
logo_url: readNullableAbsoluteHttpUrl(branding, 'logo_url', 'appPublic.branding.logo_url'),
|
||||||
wordmark_url: readNullableAbsoluteHttpUrl(branding, 'wordmark_url', 'appPublic.branding.wordmark_url'),
|
wordmark_url: readNullableAbsoluteHttpUrl(branding, 'wordmark_url', 'appPublic.branding.wordmark_url'),
|
||||||
favicon_url: readNullableAbsoluteHttpUrl(branding, 'favicon_url', 'appPublic.branding.favicon_url'),
|
favicon_url: readNullableAbsoluteHttpUrl(branding, 'favicon_url', 'appPublic.branding.favicon_url'),
|
||||||
theme_color: readNullableNonEmptyString(branding, 'theme_color', 'appPublic.branding.theme_color'),
|
theme_color: normalizeThemeColor(branding.theme_color),
|
||||||
status_page_url: readOptionalText(branding, 'status_page_url'),
|
status_page_url: readOptionalText(branding, 'status_page_url'),
|
||||||
status_page_incident_history_url: readOptionalText(branding, 'status_page_incident_history_url'),
|
status_page_incident_history_url: readOptionalText(branding, 'status_page_incident_history_url'),
|
||||||
premium_product_name: readOptionalText(branding, 'premium_product_name') ?? DEFAULT_PREMIUM_PRODUCT_NAME,
|
premium_product_name: readOptionalText(branding, 'premium_product_name') ?? DEFAULT_PREMIUM_PRODUCT_NAME,
|
||||||
|
|||||||
@@ -10,19 +10,11 @@ export const INSTANCE_BRAND_VARIABLES = [
|
|||||||
|
|
||||||
type InstanceBrandVariable = (typeof INSTANCE_BRAND_VARIABLES)[number];
|
type InstanceBrandVariable = (typeof INSTANCE_BRAND_VARIABLES)[number];
|
||||||
|
|
||||||
const HEX_COLOR_PATTERN = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
|
const HEX_COLOR_PATTERN = /^#[0-9a-f]{6}$/;
|
||||||
|
|
||||||
function parseHexColor(value: string): [number, number, number] | null {
|
function parseHexColor(value: string): [number, number, number] | null {
|
||||||
const trimmed = value.trim();
|
if (!HEX_COLOR_PATTERN.test(value)) return null;
|
||||||
if (!HEX_COLOR_PATTERN.test(trimmed)) return null;
|
const digits = value.slice(1);
|
||||||
const digits =
|
|
||||||
trimmed.length === 4
|
|
||||||
? trimmed
|
|
||||||
.slice(1)
|
|
||||||
.split('')
|
|
||||||
.map((digit) => digit + digit)
|
|
||||||
.join('')
|
|
||||||
: trimmed.slice(1);
|
|
||||||
return [
|
return [
|
||||||
Number.parseInt(digits.slice(0, 2), 16),
|
Number.parseInt(digits.slice(0, 2), 16),
|
||||||
Number.parseInt(digits.slice(2, 4), 16),
|
Number.parseInt(digits.slice(2, 4), 16),
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
const HEX_THEME_COLOR_PATTERN = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
||||||
|
const UNSET_THEME_COLOR = '#000000';
|
||||||
|
|
||||||
|
export function normalizeThemeColor(value: unknown): string | null {
|
||||||
|
if (typeof value !== 'string') return null;
|
||||||
|
const match = HEX_THEME_COLOR_PATTERN.exec(value.trim());
|
||||||
|
if (match === null) return null;
|
||||||
|
const digits = match[1].toLowerCase();
|
||||||
|
const color = `#${digits.length === 3 ? digits.replace(/./g, '$&$&') : digits}`;
|
||||||
|
return color === UNSET_THEME_COLOR ? null : color;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user