Compare commits

2 Commits

Author SHA1 Message Date
shuaiplus d5c2ab2b0f refactor: remove unused TOTP styling for cleaner code 2026-06-16 19:26:21 +08:00
shuaiplus 9e0908f43c feat: enhance TOTP formatting and improve responsive styles for TOTP codes display 2026-06-16 19:17:05 +08:00
5 changed files with 41 additions and 14 deletions
+1 -8
View File
@@ -6,7 +6,7 @@ import { t } from '@/lib/i18n';
import type { Cipher } from '@/lib/types';
import LoadingState from '@/components/LoadingState';
import WebsiteIcon from '@/components/vault/WebsiteIcon';
import { isCipherVisibleInNormalVault } from '@/components/vault/vault-page-helpers';
import { formatTotp, isCipherVisibleInNormalVault } from '@/components/vault/vault-page-helpers';
interface TotpCodesPageProps {
ciphers: Cipher[];
@@ -26,13 +26,6 @@ function getTotpTimeState(): { windowId: number; remain: number } {
};
}
function formatTotp(code: string): string {
if (!code) return code;
if (code.length === 5) return `${code.slice(0, 2)} ${code.slice(2)}`;
if (code.length < 6) return code;
return `${code.slice(0, 3)} ${code.slice(3, 6)}`;
}
function TotpListIcon({ cipher }: { cipher: Cipher }) {
return <WebsiteIcon cipher={cipher} fallback={<Globe size={18} />} />;
}
@@ -507,8 +507,9 @@ export function maskSecret(value: string): string {
export function formatTotp(code: string): string {
if (!code) return code;
if (code.length === 5) return `${code.slice(0, 2)} ${code.slice(2)}`;
if (code.length < 6) return code;
return `${code.slice(0, 3)} ${code.slice(3, 6)}`;
if (code.length <= 4) return code;
if (code.length === 8) return `${code.slice(0, 4)} ${code.slice(4)}`;
return code.replace(/(.{3})(?=.)/g, '$1 ');
}
export function formatHistoryTime(value: string | null | undefined): string {
+31 -2
View File
@@ -220,6 +220,25 @@ function normalizeTotpSecret(secret: string): string {
return secret.toUpperCase().replace(/[\s-]/g, '').replace(/=+$/g, '');
}
function readOtpAuthParam(raw: string, name: string): string {
const queryStart = raw.indexOf('?');
if (queryStart < 0) return '';
const fragmentStart = raw.indexOf('#', queryStart + 1);
const query = raw.slice(queryStart + 1, fragmentStart > queryStart ? fragmentStart : undefined);
for (const part of query.split('&')) {
const eq = part.indexOf('=');
const key = eq >= 0 ? part.slice(0, eq) : part;
if (key.trim().toLowerCase() !== name.toLowerCase()) continue;
const value = eq >= 0 ? part.slice(eq + 1) : '';
try {
return decodeURIComponent(value.replace(/\+/g, ' '));
} catch {
return value;
}
}
return '';
}
function parseSteamSecret(raw: string): string {
const match = raw.trim().match(/^steam:\/\/([^/?#]+)(?:[/?#].*)?$/i);
if (!match?.[1]) return '';
@@ -276,7 +295,8 @@ function parseTotpConfig(raw: string): TotpConfig {
if (/^otpauth:\/\//i.test(s)) {
try {
const u = new URL(s);
if (u.hostname.toLowerCase() !== 'totp') {
const otpType = u.hostname.toLowerCase();
if (otpType !== 'totp') {
return { secret: '', steam: false, ...DEFAULT_TOTP_CONFIG };
}
const label = decodeURIComponent((u.pathname || '').replace(/^\/+/, '')).toLowerCase();
@@ -291,7 +311,16 @@ function parseTotpConfig(raw: string): TotpConfig {
period: parseTotpPositiveInt(u.searchParams.get('period'), DEFAULT_TOTP_CONFIG.period, 1, 3600),
};
} catch {
return { secret: '', steam: false, ...DEFAULT_TOTP_CONFIG };
const issuer = readOtpAuthParam(s, 'issuer').trim().toLowerCase();
const algorithm = readOtpAuthParam(s, 'algorithm').trim().toLowerCase();
const steam = issuer === 'steam' || algorithm === 'steam';
return {
secret: normalizeTotpSecret(readOtpAuthParam(s, 'secret')),
steam,
algorithm: steam ? 'SHA-1' : parseTotpHashAlgorithm(algorithm),
digits: steam ? 5 : parseTotpPositiveInt(readOtpAuthParam(s, 'digits'), DEFAULT_TOTP_CONFIG.digits, 1, 10),
period: parseTotpPositiveInt(readOtpAuthParam(s, 'period'), DEFAULT_TOTP_CONFIG.period, 1, 3600),
};
}
}
return { secret: normalizeTotpSecret(s), steam: false, ...DEFAULT_TOTP_CONFIG };
+4
View File
@@ -892,6 +892,10 @@
font-size: 13px;
}
.totp-code-main strong {
font-size: 20px;
}
.settings-module .field,
.auth-card .field {
margin-bottom: 8px;
+2 -2
View File
@@ -952,7 +952,7 @@ select.input.duplicate-mode-toolbar-select {
.totp-codes-list {
@apply grid w-full items-start gap-2.5;
grid-template-columns: repeat(var(--totp-columns, 1), minmax(300px, 1fr));
grid-template-columns: repeat(var(--totp-columns, 1), minmax(0, 1fr));
}
.totp-code-row {
@@ -977,7 +977,7 @@ select.input.duplicate-mode-toolbar-select {
}
.totp-code-main strong {
@apply whitespace-nowrap text-[22px] leading-none;
@apply min-w-0 whitespace-nowrap text-[22px] leading-none;
letter-spacing: 0.04em;
}