Fix Codex OAuth configuration and refine UI controls
This commit is contained in:
1 parent
1acbeb6f0a
commit
2c7030734e
7 files changed
+110
-115
No files matched your search
+49
-24
@@ -584,6 +584,7 @@ struct AgentConfigStatus {
|
||||
config_valid: bool,
|
||||
configured: bool,
|
||||
current_model: Option<String>,
|
||||
oauth_configuration: bool,
|
||||
modification_enabled: bool,
|
||||
modification_state: String,
|
||||
backup_available: bool,
|
||||
@@ -2422,9 +2423,11 @@ fn inspect_agent_config(
|
||||
let paths = agent_config_paths(client, home);
|
||||
let config_exists = paths.iter().any(|path| path.is_file());
|
||||
let result = inspect_agent_managed_config(client, &paths, port, api_key);
|
||||
let (configured, current_model, config_valid, error) = match result {
|
||||
Ok((configured, model)) => (configured, model, true, None),
|
||||
Err(error) => (false, None, false, Some(error)),
|
||||
let (configured, current_model, oauth_configuration, config_valid, error) = match result {
|
||||
Ok((configured, model, oauth_configuration)) => {
|
||||
(configured, model, oauth_configuration, true, None)
|
||||
}
|
||||
Err(error) => (false, None, false, false, Some(error)),
|
||||
};
|
||||
let launch_targets = agent_launch_targets(client, home);
|
||||
let executable = find_agent_executable(client, home);
|
||||
@@ -2458,6 +2461,7 @@ fn inspect_agent_config(
|
||||
config_valid,
|
||||
configured,
|
||||
current_model,
|
||||
oauth_configuration,
|
||||
modification_enabled: modification.enabled,
|
||||
modification_state: modification.state,
|
||||
backup_available: modification.backup_available,
|
||||
@@ -2498,17 +2502,22 @@ fn inspect_agent_managed_config(
|
||||
paths: &[PathBuf],
|
||||
port: u16,
|
||||
api_key: &str,
|
||||
) -> Result<(bool, Option<String>), String> {
|
||||
) -> Result<(bool, Option<String>, bool), String> {
|
||||
match client {
|
||||
AgentClient::ClaudeCode => inspect_claude_agent_config(&paths[0], port, api_key),
|
||||
AgentClient::ClaudeCode => inspect_claude_agent_config(&paths[0], port, api_key)
|
||||
.map(|(configured, model)| (configured, model, false)),
|
||||
AgentClient::ClaudeDesktop if client.supported_platform() => {
|
||||
inspect_claude_desktop_agent_config(paths, port, api_key)
|
||||
.map(|(configured, model)| (configured, model, false))
|
||||
}
|
||||
AgentClient::ClaudeDesktop => Ok((false, None)),
|
||||
AgentClient::ClaudeDesktop => Ok((false, None, false)),
|
||||
AgentClient::Codex => inspect_codex_agent_config(&paths[0], port, api_key),
|
||||
AgentClient::OpenCode => inspect_opencode_agent_config(&paths[0], port, api_key),
|
||||
AgentClient::OpenClaw => inspect_openclaw_agent_config(&paths[0], port, api_key),
|
||||
AgentClient::Hermes => inspect_hermes_agent_config(&paths[0], port, api_key),
|
||||
AgentClient::OpenCode => inspect_opencode_agent_config(&paths[0], port, api_key)
|
||||
.map(|(configured, model)| (configured, model, false)),
|
||||
AgentClient::OpenClaw => inspect_openclaw_agent_config(&paths[0], port, api_key)
|
||||
.map(|(configured, model)| (configured, model, false)),
|
||||
AgentClient::Hermes => inspect_hermes_agent_config(&paths[0], port, api_key)
|
||||
.map(|(configured, model)| (configured, model, false)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3410,9 +3419,9 @@ fn inspect_codex_agent_config(
|
||||
path: &Path,
|
||||
port: u16,
|
||||
api_key: &str,
|
||||
) -> Result<(bool, Option<String>), String> {
|
||||
) -> Result<(bool, Option<String>, bool), String> {
|
||||
if !path.is_file() {
|
||||
return Ok((false, None));
|
||||
return Ok((false, None, false));
|
||||
}
|
||||
let root: toml::Value = toml::from_str(
|
||||
&fs::read_to_string(path).map_err(|error| format!("读取 Codex 配置失败: {error}"))?,
|
||||
@@ -3428,11 +3437,10 @@ fn inspect_codex_agent_config(
|
||||
.and_then(|provider| provider.get("experimental_bearer_token"))
|
||||
.and_then(toml::Value::as_str)
|
||||
== Some(api_key);
|
||||
let uses_oauth = provider
|
||||
let oauth_configuration = provider
|
||||
.and_then(|provider| provider.get("requires_openai_auth"))
|
||||
.and_then(toml::Value::as_bool)
|
||||
== Some(true)
|
||||
&& provider.is_some_and(|provider| !provider.contains_key("experimental_bearer_token"));
|
||||
== Some(true);
|
||||
let configured = root.get("model_provider").and_then(toml::Value::as_str)
|
||||
== Some(MANAGED_AGENT_PROVIDER_ID)
|
||||
&& root.get("model_catalog_json").and_then(toml::Value::as_str)
|
||||
@@ -3445,7 +3453,7 @@ fn inspect_codex_agent_config(
|
||||
.and_then(|provider| provider.get("base_url"))
|
||||
.and_then(toml::Value::as_str)
|
||||
== Some(expected_base.as_str())
|
||||
&& (uses_api_key || uses_oauth)
|
||||
&& uses_api_key
|
||||
&& provider
|
||||
.and_then(|provider| provider.get("wire_api"))
|
||||
.and_then(toml::Value::as_str)
|
||||
@@ -3456,7 +3464,7 @@ fn inspect_codex_agent_config(
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(str::to_string);
|
||||
Ok((configured, model))
|
||||
Ok((configured, model, oauth_configuration))
|
||||
}
|
||||
|
||||
fn inspect_opencode_agent_config(
|
||||
@@ -4017,12 +4025,11 @@ fn build_codex_agent_config_with_oauth(
|
||||
set_codex_table_item(provider, "name", value("EasyCLIProxyAPI"));
|
||||
set_codex_table_item(provider, "base_url", value(base_url));
|
||||
set_codex_table_item(provider, "wire_api", value("responses"));
|
||||
set_codex_table_item(provider, "experimental_bearer_token", value(api_key));
|
||||
if oauth_configuration {
|
||||
provider.remove("experimental_bearer_token");
|
||||
set_codex_table_item(provider, "requires_openai_auth", value(true));
|
||||
} else {
|
||||
provider.remove("requires_openai_auth");
|
||||
set_codex_table_item(provider, "experimental_bearer_token", value(api_key));
|
||||
}
|
||||
let rendered = document.to_string();
|
||||
toml::from_str::<toml::Value>(&rendered)
|
||||
@@ -5771,7 +5778,8 @@ fn enable_agent_modification(
|
||||
Vec::new(),
|
||||
));
|
||||
}
|
||||
let (_, current_model) = inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?;
|
||||
let (_, current_model, _) =
|
||||
inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?;
|
||||
if agent_has_managed_marker(client, &paths)? {
|
||||
if let Some(current_model) = current_model.as_deref() {
|
||||
if let Some(record) = build_legacy_agent_record(client, home, port, current_model)? {
|
||||
@@ -5849,7 +5857,8 @@ fn disable_agent_modification(
|
||||
let mut record = match load_agent_record(client, &paths)? {
|
||||
Some(record) => record,
|
||||
None => {
|
||||
let (_, model) = inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?;
|
||||
let (_, model, _) =
|
||||
inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?;
|
||||
if !agent_has_managed_marker(client, &paths)? {
|
||||
return Ok(action_result(
|
||||
"disabled",
|
||||
@@ -5934,7 +5943,7 @@ fn update_agent_modification(
|
||||
let record = match load_agent_record(client, &paths)? {
|
||||
Some(record) => record,
|
||||
None => {
|
||||
let (_, current_model) =
|
||||
let (_, current_model, _) =
|
||||
inspect_agent_managed_config(client, &paths, port, DEFAULT_API_KEY)?;
|
||||
if !agent_has_managed_marker(client, &paths)? {
|
||||
return Err("请先应用配置修改".to_string());
|
||||
@@ -13938,7 +13947,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_oauth_configuration_uses_openai_auth_without_bearer_token() {
|
||||
fn codex_oauth_configuration_uses_openai_auth_with_bearer_token() {
|
||||
let home = agent_test_home("codex-oauth-configuration");
|
||||
let path = home.join(".codex/config.toml");
|
||||
let rendered = build_codex_agent_config_with_oauth(
|
||||
Some("[model_providers.cpa-gui]\nexperimental_bearer_token = \"old-key\"\n"),
|
||||
"http://127.0.0.1:8317/v1",
|
||||
@@ -13958,7 +13969,21 @@ mod tests {
|
||||
.and_then(toml::Value::as_bool),
|
||||
Some(true)
|
||||
);
|
||||
assert!(!provider.contains_key("experimental_bearer_token"));
|
||||
assert_eq!(
|
||||
provider
|
||||
.get("experimental_bearer_token")
|
||||
.and_then(toml::Value::as_str),
|
||||
Some(DEFAULT_API_KEY)
|
||||
);
|
||||
|
||||
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
fs::write(&path, rendered).unwrap();
|
||||
let (configured, model, oauth_configuration) =
|
||||
inspect_codex_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap();
|
||||
assert!(configured);
|
||||
assert_eq!(model.as_deref(), Some("gpt-test"));
|
||||
assert!(oauth_configuration);
|
||||
fs::remove_dir_all(home).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -14620,7 +14645,7 @@ custom_option = "keep-original"
|
||||
provider["custom_option"] = toml_edit::value("keep-updated");
|
||||
fs::write(&path, externally_edited.to_string()).unwrap();
|
||||
|
||||
let (configured, current_model) =
|
||||
let (configured, current_model, _) =
|
||||
inspect_codex_agent_config(&path, 8317, DEFAULT_API_KEY).unwrap();
|
||||
let inspection = inspect_agent_modification(
|
||||
AgentClient::Codex,
|
||||
|
||||
+23
-17
@@ -14,12 +14,10 @@ import {
|
||||
Languages,
|
||||
LogIn,
|
||||
MessageCircle,
|
||||
Moon,
|
||||
Network,
|
||||
PackageOpen,
|
||||
ServerCog,
|
||||
Settings,
|
||||
Sun,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import appLogo from './assets/logo.jpg';
|
||||
@@ -308,22 +306,30 @@ function AppContent() {
|
||||
</nav>
|
||||
|
||||
<div className="sidebar-bottom">
|
||||
<button
|
||||
type="button"
|
||||
className="sidebar-theme-toggle"
|
||||
aria-label={theme === 'dark' ? t('app.theme.switchToLight') : t('app.theme.switchToDark')}
|
||||
aria-pressed={theme === 'dark'}
|
||||
title={theme === 'dark' ? t('app.theme.switchToLight') : t('app.theme.switchToDark')}
|
||||
onClick={() => setTheme((current) => current === 'dark' ? 'light' : 'dark')}
|
||||
<div
|
||||
className="sidebar-theme-selector"
|
||||
role="group"
|
||||
aria-label={`${t('app.theme.light')} / ${t('app.theme.dark')}`}
|
||||
>
|
||||
{theme === 'dark'
|
||||
? <Moon size={16} aria-hidden="true" />
|
||||
: <Sun size={16} aria-hidden="true" />}
|
||||
<span>{theme === 'dark' ? t('app.theme.dark') : t('app.theme.light')}</span>
|
||||
<i className="theme-switch-track" aria-hidden="true">
|
||||
<b />
|
||||
</i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={theme === 'light' ? 'active' : ''}
|
||||
aria-pressed={theme === 'light'}
|
||||
title={t('app.theme.switchToLight')}
|
||||
onClick={() => setTheme('light')}
|
||||
>
|
||||
{t('app.theme.light')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={theme === 'dark' ? 'active' : ''}
|
||||
aria-pressed={theme === 'dark'}
|
||||
title={t('app.theme.switchToDark')}
|
||||
onClick={() => setTheme('dark')}
|
||||
>
|
||||
{t('app.theme.dark')}
|
||||
</button>
|
||||
</div>
|
||||
<div ref={languageMenuRef} className="sidebar-language">
|
||||
<button
|
||||
ref={languageButtonRef}
|
||||
|
||||
@@ -445,8 +445,6 @@ export const jaOverrides = {
|
||||
'agents.sessions.repairTitle': '過去のセッションを復元',
|
||||
'agents.sessions.repairDescription': '他の Provider のセッションを現在のセッション Provider に同期します。',
|
||||
'agents.sessions.autoRepair': 'Codex 起動前に過去のセッションを自動復元',
|
||||
'agents.sessions.autoRepairEnabled': '起動前の自動復元を有効にしました。',
|
||||
'agents.sessions.autoRepairDisabled': '起動前の自動復元を無効にしました。',
|
||||
'agents.sessions.settingLoading': '読み込み中',
|
||||
'agents.sessions.settingOn': 'オン',
|
||||
'agents.sessions.settingOff': 'オフ',
|
||||
|
||||
@@ -448,8 +448,6 @@ export const zhCN = {
|
||||
'agents.sessions.repairTitle': '恢复历史会话',
|
||||
'agents.sessions.repairDescription': '将其他渠道的会话同步到当前会话',
|
||||
'agents.sessions.autoRepair': '启动 Codex 前自动恢复历史会话',
|
||||
'agents.sessions.autoRepairEnabled': '已开启启动前自动恢复历史会话。',
|
||||
'agents.sessions.autoRepairDisabled': '已关闭启动前自动恢复历史会话。',
|
||||
'agents.sessions.settingLoading': '读取中',
|
||||
'agents.sessions.settingOn': '已开启',
|
||||
'agents.sessions.settingOff': '已关闭',
|
||||
@@ -1293,8 +1291,6 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.sessions.repairTitle': 'Restore Historical Sessions',
|
||||
'agents.sessions.repairDescription': 'Sync sessions from other providers to the current session provider.',
|
||||
'agents.sessions.autoRepair': 'Restore historical sessions before launching Codex',
|
||||
'agents.sessions.autoRepairEnabled': 'Automatic restoration before launch is enabled.',
|
||||
'agents.sessions.autoRepairDisabled': 'Automatic restoration before launch is disabled.',
|
||||
'agents.sessions.settingLoading': 'Loading',
|
||||
'agents.sessions.settingOn': 'On',
|
||||
'agents.sessions.settingOff': 'Off',
|
||||
|
||||
@@ -62,6 +62,7 @@ type AgentConfigStatus = {
|
||||
configValid: boolean;
|
||||
configured: boolean;
|
||||
currentModel: string | null;
|
||||
oauthConfiguration: boolean;
|
||||
modificationEnabled: boolean;
|
||||
modificationState: AgentModificationState;
|
||||
backupAvailable: boolean;
|
||||
@@ -509,7 +510,7 @@ export function AgentsPage() {
|
||||
const [closeAppError, setCloseAppError] = useState('');
|
||||
const [closeAppNotice, setCloseAppNotice] = useState('');
|
||||
const [closeAppConfirmOpen, setCloseAppConfirmOpen] = useState(false);
|
||||
const [oauthConfiguration, setOauthConfiguration] = useState(false);
|
||||
const [oauthConfigurationDraft, setOauthConfigurationDraft] = useState<boolean | null>(null);
|
||||
const autoLoadedModelClientRef = useRef<AgentClientId | null>(null);
|
||||
|
||||
const loadStatuses = useCallback(async (forceRefresh = false) => {
|
||||
@@ -620,6 +621,9 @@ export function AgentsPage() {
|
||||
const activeDefinition = agentDefinitions.find((agent) => agent.id === selected)
|
||||
?? agentDefinitions[0];
|
||||
const activeStatus = statuses.find((status) => status.id === selected) ?? null;
|
||||
const oauthConfiguration = oauthConfigurationDraft
|
||||
?? activeStatus?.oauthConfiguration
|
||||
?? false;
|
||||
const savedSelectedModel = modelByClient[selected] ?? '';
|
||||
const selectedModelOption = findAgentModel(models, savedSelectedModel);
|
||||
const selectedModel = selectedModelOption?.name ?? '';
|
||||
@@ -629,11 +633,14 @@ export function AgentsPage() {
|
||||
(target) => target.id === selectedLaunchTargetId,
|
||||
) ?? activeLaunchTargets[0] ?? null;
|
||||
const appliedModel = activeStatus?.appliedModel ?? activeStatus?.currentModel ?? '';
|
||||
const draftChanged = Boolean(
|
||||
const modelDraftChanged = Boolean(
|
||||
selectedModel.trim()
|
||||
&& appliedModel.trim()
|
||||
&& selectedModel.trim() !== appliedModel.trim(),
|
||||
);
|
||||
const oauthConfigurationChanged = selected === 'codex'
|
||||
&& oauthConfiguration !== Boolean(activeStatus?.oauthConfiguration);
|
||||
const draftChanged = modelDraftChanged || oauthConfigurationChanged;
|
||||
const canEnable = Boolean(
|
||||
activeStatus?.supportedPlatform
|
||||
&& activeStatus.installed
|
||||
@@ -730,6 +737,7 @@ export function AgentsPage() {
|
||||
oauthConfiguration,
|
||||
});
|
||||
await reloadStatusesAfterAction();
|
||||
setOauthConfigurationDraft(null);
|
||||
} catch (requestError) {
|
||||
setConfigurationError(String(requestError));
|
||||
} finally {
|
||||
@@ -751,6 +759,7 @@ export function AgentsPage() {
|
||||
});
|
||||
setDefaultConfirmOpen(false);
|
||||
await reloadStatusesAfterAction();
|
||||
setOauthConfigurationDraft(null);
|
||||
} catch (requestError) {
|
||||
setDefaultError(String(requestError));
|
||||
} finally {
|
||||
@@ -767,6 +776,7 @@ export function AgentsPage() {
|
||||
setClearConfirmOpen(false);
|
||||
setClearNotice(t('agents.clear.success'));
|
||||
await reloadStatusesAfterAction();
|
||||
setOauthConfigurationDraft(null);
|
||||
} catch (requestError) {
|
||||
setClearError(String(requestError));
|
||||
} finally {
|
||||
@@ -942,7 +952,7 @@ export function AgentsPage() {
|
||||
<section className="agent-core-setting-section agent-model-section">
|
||||
<div className="agent-section-heading">
|
||||
<div><strong>{t('agents.useModel')}</strong></div>
|
||||
{draftChanged ? <span className="agent-pending-badge">{t('agents.pending')}</span> : null}
|
||||
{modelDraftChanged ? <span className="agent-pending-badge">{t('agents.pending')}</span> : null}
|
||||
</div>
|
||||
<AgentModelPicker
|
||||
models={models}
|
||||
@@ -983,7 +993,7 @@ export function AgentsPage() {
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
checked={oauthConfiguration}
|
||||
onChange={(event) => setOauthConfiguration(event.currentTarget.checked)}
|
||||
onChange={(event) => setOauthConfigurationDraft(event.currentTarget.checked)}
|
||||
disabled={busy}
|
||||
aria-label={t('agents.modify.oauthConfiguration')}
|
||||
/>
|
||||
|
||||
@@ -8,7 +8,6 @@ type RepairOnLaunchSetting = {
|
||||
};
|
||||
|
||||
type StatusMessage = {
|
||||
kind: 'success' | 'error';
|
||||
text: string;
|
||||
};
|
||||
|
||||
@@ -32,7 +31,6 @@ export function CodexSessionAutoRestoreCard() {
|
||||
.catch((error) => {
|
||||
if (!disposed) {
|
||||
setStatus({
|
||||
kind: 'error',
|
||||
text: t('agents.sessions.settingLoadFailed', { error: errorMessage(error) }),
|
||||
});
|
||||
}
|
||||
@@ -53,15 +51,8 @@ export function CodexSessionAutoRestoreCard() {
|
||||
request: { enabled: nextEnabled },
|
||||
});
|
||||
setEnabled(result.enabled);
|
||||
setStatus({
|
||||
kind: 'success',
|
||||
text: result.enabled
|
||||
? t('agents.sessions.autoRepairEnabled')
|
||||
: t('agents.sessions.autoRepairDisabled'),
|
||||
});
|
||||
} catch (error) {
|
||||
setStatus({
|
||||
kind: 'error',
|
||||
text: t('agents.sessions.settingFailed', { error: errorMessage(error) }),
|
||||
});
|
||||
} finally {
|
||||
@@ -102,8 +93,8 @@ export function CodexSessionAutoRestoreCard() {
|
||||
</div>
|
||||
{status ? (
|
||||
<small
|
||||
className={`codex-auto-restore-message ${status.kind}`}
|
||||
role={status.kind === 'error' ? 'alert' : 'status'}
|
||||
className="codex-auto-restore-message error"
|
||||
role="alert"
|
||||
>
|
||||
{status.text}
|
||||
</small>
|
||||
|
||||
+22
-53
@@ -440,71 +440,40 @@ input:focus-visible {
|
||||
border-top: 1px solid var(--theme-e3e1db);
|
||||
}
|
||||
|
||||
.sidebar-theme-toggle {
|
||||
.sidebar-theme-selector {
|
||||
display: grid;
|
||||
grid-template-columns: 18px minmax(0, 1fr) 36px;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 3px;
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 0 10px;
|
||||
padding: 3px;
|
||||
border: 1px solid var(--theme-d8d4cc);
|
||||
border-radius: 8px;
|
||||
background: var(--theme-fffdf8);
|
||||
color: var(--theme-716b61);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.sidebar-theme-toggle:hover {
|
||||
border-color: var(--theme-c7c2b8);
|
||||
.sidebar-theme-selector button {
|
||||
min-width: 0;
|
||||
min-height: 32px;
|
||||
padding: 0 8px;
|
||||
border: 0;
|
||||
border-radius: 5px;
|
||||
background: transparent;
|
||||
color: var(--theme-716b61);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
transition: background 140ms ease, color 140ms ease, box-shadow 140ms ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-theme-selector button:hover {
|
||||
background: var(--theme-e8e5dc);
|
||||
color: var(--theme-2d2a26);
|
||||
}
|
||||
|
||||
.sidebar-theme-toggle > svg {
|
||||
color: var(--theme-7a746c);
|
||||
}
|
||||
|
||||
.sidebar-theme-toggle > span {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.theme-switch-track {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
border: 1px solid var(--theme-c7c2b8);
|
||||
border-radius: 999px;
|
||||
.sidebar-theme-selector button.active {
|
||||
background: var(--theme-dfdcd4);
|
||||
transition: border-color 160ms ease, background 160ms ease;
|
||||
}
|
||||
|
||||
.theme-switch-track b {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
display: block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
background: var(--theme-fff);
|
||||
box-shadow: 0 1px 3px rgba(45, 42, 38, 0.24);
|
||||
transition: transform 180ms ease, background 160ms ease;
|
||||
}
|
||||
|
||||
.sidebar-theme-toggle[aria-pressed='true'] .theme-switch-track {
|
||||
border-color: var(--theme-8069ad);
|
||||
background: var(--theme-654f91);
|
||||
}
|
||||
|
||||
.sidebar-theme-toggle[aria-pressed='true'] .theme-switch-track b {
|
||||
transform: translateX(16px);
|
||||
box-shadow: inset 0 0 0 1px var(--theme-d5d2cb);
|
||||
color: var(--theme-2d2a26);
|
||||
}
|
||||
|
||||
.sidebar-language {
|
||||
|
||||
Reference in new issue
Block a user