Spaces:
Running
Running
Upload 104 files
Browse files- 前端页面/core/网络请求_缓存管理.js +1 -0
- 前端页面/market/创作者卡片组件.js +67 -26
- 前端页面/profile/个人中心视图.js +41 -14
- 前端页面/styles/主题基础.css +8 -5
前端页面/core/网络请求_缓存管理.js
CHANGED
|
@@ -844,6 +844,7 @@ export function invalidateRelatedCache(endpoint, method = null) {
|
|
| 844 |
|
| 845 |
// 获取缓存TTL
|
| 846 |
export function _getCacheTTL(endpoint) {
|
|
|
|
| 847 |
for (const [pattern, ttl] of Object.entries(CACHE_CONFIG)) {
|
| 848 |
if (endpoint.startsWith(pattern)) {
|
| 849 |
return ttl;
|
|
|
|
| 844 |
|
| 845 |
// 获取缓存TTL
|
| 846 |
export function _getCacheTTL(endpoint) {
|
| 847 |
+
if (endpoint.split(/[?#]/)[0] === CACHE.PUBLIC_LISTS.prompts.endpoint) return CACHE.TTL.DETAIL_DATA;
|
| 848 |
for (const [pattern, ttl] of Object.entries(CACHE_CONFIG)) {
|
| 849 |
if (endpoint.startsWith(pattern)) {
|
| 850 |
return ttl;
|
前端页面/market/创作者卡片组件.js
CHANGED
|
@@ -345,7 +345,7 @@ export function createCreatorCard(creatorData, currentUser = null) {
|
|
| 345 |
let commentUI = null;
|
| 346 |
let isDetailLoaded = false;
|
| 347 |
let isChartRendered = false;
|
| 348 |
-
let chartInstance = null;
|
| 349 |
let generation = 0, detailPending = false, chartPending = false, detailRead = null;
|
| 350 |
let widthObserver = null, widthTimer = null, resizeTimer = null;
|
| 351 |
let stopTrackingRemoval = null, resizeSubscribed = false;
|
|
@@ -368,6 +368,9 @@ export function createCreatorCard(creatorData, currentUser = null) {
|
|
| 368 |
});
|
| 369 |
const current = token => scope.isCurrent() && card.isConnected && detailView.style.display !== 'none' && token === generation;
|
| 370 |
const clearChartWork = () => {
|
|
|
|
|
|
|
|
|
|
| 371 |
widthObserver?.disconnect(); widthObserver = null;
|
| 372 |
clearTimeout(widthTimer); clearTimeout(resizeTimer); widthTimer = resizeTimer = null;
|
| 373 |
chartPending = false;
|
|
@@ -487,40 +490,78 @@ export function createCreatorCard(creatorData, currentUser = null) {
|
|
| 487 |
renderTrendChart(detailData.trendData);
|
| 488 |
}
|
| 489 |
|
| 490 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
function _initTrendChart(chartDom, trendData, token) {
|
| 492 |
if (!current(token)) return;
|
| 493 |
loadECharts().then(echarts => {
|
| 494 |
if (!current(token) || !chartDom.isConnected) return;
|
| 495 |
chartDom.innerHTML = "";
|
| 496 |
-
const theme = getComputedStyle(card);
|
| 497 |
-
const textColor = theme.getPropertyValue('--rk-text').trim() || theme.color;
|
| 498 |
-
const borderColor = theme.getPropertyValue('--rk-border').trim() || theme.borderColor;
|
| 499 |
chartInstance = echarts.init(chartDom, null, { backgroundColor: 'transparent' });
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
chartInstance.setOption({
|
| 504 |
-
tooltip: { trigger: 'axis', textStyle: { fontSize: 11 } },
|
| 505 |
-
grid: { top: 10, bottom: 20, left: 35, right: 10, containLabel: true },
|
| 506 |
-
xAxis: { type: 'category', data: data.months, axisLabel: { color: textColor, fontSize: 10 }, axisLine: { lineStyle: { color: borderColor } } },
|
| 507 |
-
yAxis: { type: 'value', splitLine: { lineStyle: { color: borderColor, type: 'dashed' } }, axisLabel: { color: textColor, fontSize: 10 }, minInterval: 1 },
|
| 508 |
-
series: [
|
| 509 |
-
{ name: t('creator.chart.series.tools'), type: 'line', data: data.tools, smooth: true, itemStyle: { color: '#4CAF50' }, lineStyle: { width: 2, type: 'dashed' } },
|
| 510 |
-
{ name: t('creator.chart.series.skills'), type: 'line', data: data.skills || [], smooth: true, itemStyle: { color: '#7C4DFF' }, lineStyle: { width: 2, type: 'dashed' } },
|
| 511 |
-
{ name: t('creator.chart.series.apps'), type: 'line', data: data.apps, smooth: true, itemStyle: { color: '#2196F3' }, lineStyle: { width: 2, type: 'dashed' } },
|
| 512 |
-
{ name: t('creator.chart.series.recommends'), type: 'line', data: data.recommends, smooth: true, itemStyle: { color: '#FF9800' }, lineStyle: { width: 2, type: 'dashed' } }
|
| 513 |
-
]
|
| 514 |
-
});
|
| 515 |
-
isChartRendered = true;
|
| 516 |
-
|
| 517 |
-
// 初始化完成后延迟调用resize确保正确渲染
|
| 518 |
resizeTimer = setTimeout(() => {
|
| 519 |
resizeTimer = null;
|
| 520 |
if (current(token) && chartInstance) chartInstance.resize();
|
| 521 |
}, 300);
|
| 522 |
}).catch(() => {
|
| 523 |
if (!current(token)) return;
|
|
|
|
| 524 |
chartInstance?.dispose(); chartInstance = null; isChartRendered = false;
|
| 525 |
chartDom.innerHTML = `<div class="creator-card__chart-status creator-card__chart-status--error" role="status">${t('creator.chart.load_failed')}</div>`;
|
| 526 |
}).finally(() => { if (current(token)) chartPending = false; });
|
|
@@ -529,11 +570,11 @@ export function createCreatorCard(creatorData, currentUser = null) {
|
|
| 529 |
function renderTrendChart(trendData) {
|
| 530 |
const token = generation;
|
| 531 |
if (!current(token)) return;
|
| 532 |
-
|
|
|
|
|
|
|
| 533 |
if (chartPending) return;
|
| 534 |
chartPending = true;
|
| 535 |
-
const chartDom = detailView.querySelector(`#${chartContainerId}`);
|
| 536 |
-
if (!chartDom) { chartPending = false; return; }
|
| 537 |
|
| 538 |
chartDom.innerHTML = `<div class="creator-card__chart-status" role="status">${t('creator.chart.loading')}</div>`;
|
| 539 |
|
|
|
|
| 345 |
let commentUI = null;
|
| 346 |
let isDetailLoaded = false;
|
| 347 |
let isChartRendered = false;
|
| 348 |
+
let chartInstance = null, removeChartAppearance = null;
|
| 349 |
let generation = 0, detailPending = false, chartPending = false, detailRead = null;
|
| 350 |
let widthObserver = null, widthTimer = null, resizeTimer = null;
|
| 351 |
let stopTrackingRemoval = null, resizeSubscribed = false;
|
|
|
|
| 368 |
});
|
| 369 |
const current = token => scope.isCurrent() && card.isConnected && detailView.style.display !== 'none' && token === generation;
|
| 370 |
const clearChartWork = () => {
|
| 371 |
+
removeChartAppearance?.(); removeChartAppearance = null;
|
| 372 |
+
const chartDom = detailView.querySelector('.creator-card__chart');
|
| 373 |
+
chartDom?.removeAttribute('role'); chartDom?.removeAttribute('aria-label');
|
| 374 |
widthObserver?.disconnect(); widthObserver = null;
|
| 375 |
clearTimeout(widthTimer); clearTimeout(resizeTimer); widthTimer = resizeTimer = null;
|
| 376 |
chartPending = false;
|
|
|
|
| 490 |
renderTrendChart(detailData.trendData);
|
| 491 |
}
|
| 492 |
|
| 493 |
+
function bindChartAppearance(chartDom, trendData, token) {
|
| 494 |
+
removeChartAppearance?.(); removeChartAppearance = null;
|
| 495 |
+
const instance = chartInstance;
|
| 496 |
+
const data = trendData || { months: [], tools: [], skills: [], apps: [], recommends: [] };
|
| 497 |
+
const refresh = () => {
|
| 498 |
+
if (!current(token) || chartInstance !== instance || !chartDom.isConnected) return;
|
| 499 |
+
try {
|
| 500 |
+
const theme = getComputedStyle(chartDom);
|
| 501 |
+
const textColor = theme.getPropertyValue('--rk-text').trim() || theme.color;
|
| 502 |
+
const borderColor = theme.getPropertyValue('--rk-border').trim() || theme.borderColor;
|
| 503 |
+
const panelColor = theme.getPropertyValue('--rk-panel').trim() || theme.backgroundColor;
|
| 504 |
+
const measuredSize = parseFloat(theme.fontSize);
|
| 505 |
+
const fontSize = Number.isFinite(measuredSize) ? measuredSize : 12;
|
| 506 |
+
const series = [
|
| 507 |
+
{ name: t('creator.chart.series.tools'), type: 'line', data: data.tools, smooth: true, itemStyle: { color: '#4CAF50' }, lineStyle: { width: 2, type: 'dashed' } },
|
| 508 |
+
{ name: t('creator.chart.series.skills'), type: 'line', data: data.skills || [], smooth: true, itemStyle: { color: '#7C4DFF' }, lineStyle: { width: 2, type: 'dashed' } },
|
| 509 |
+
{ name: t('creator.chart.series.apps'), type: 'line', data: data.apps, smooth: true, itemStyle: { color: '#2196F3' }, lineStyle: { width: 2, type: 'dashed' } },
|
| 510 |
+
{ name: t('creator.chart.series.recommends'), type: 'line', data: data.recommends, smooth: true, itemStyle: { color: '#FF9800' }, lineStyle: { width: 2, type: 'dashed' } }
|
| 511 |
+
];
|
| 512 |
+
instance.setOption({
|
| 513 |
+
animation: getSettings().enableAnimations !== false && !window.matchMedia('(prefers-reduced-motion: reduce)').matches,
|
| 514 |
+
textStyle: { color: textColor, fontFamily: theme.fontFamily, fontSize },
|
| 515 |
+
tooltip: { trigger: 'axis', backgroundColor: panelColor, borderColor, textStyle: { color: textColor, fontSize } },
|
| 516 |
+
grid: { top: fontSize, bottom: fontSize * 1.5, left: fontSize, right: fontSize, containLabel: true },
|
| 517 |
+
xAxis: { type: 'category', data: data.months, axisLabel: { color: textColor, fontSize, hideOverlap: true, formatter: value => String(value).replace('-', '\n'), showMinLabel: true, showMaxLabel: true, alignMinLabel: 'left', alignMaxLabel: 'right' }, axisLine: { lineStyle: { color: borderColor } } },
|
| 518 |
+
yAxis: { type: 'value', splitLine: { lineStyle: { color: borderColor, type: 'dashed' } }, axisLabel: { color: textColor, fontSize }, minInterval: 1 },
|
| 519 |
+
series
|
| 520 |
+
});
|
| 521 |
+
chartDom.setAttribute('role', 'img');
|
| 522 |
+
chartDom.setAttribute('aria-label', series.map(entry => t('chart.data_description', {
|
| 523 |
+
series: entry.name,
|
| 524 |
+
values: data.months.flatMap((month, index) => entry.data?.[index] == null ? [] : [t('chart.month_value', { month, value: entry.data[index] })]).join('; ')
|
| 525 |
+
})).join(' '));
|
| 526 |
+
instance.resize();
|
| 527 |
+
} catch (_) {
|
| 528 |
+
if (!current(token) || chartInstance !== instance) return;
|
| 529 |
+
clearChartWork(); instance.dispose(); chartInstance = null; isChartRendered = false;
|
| 530 |
+
chartDom.innerHTML = `<div class="creator-card__chart-status creator-card__chart-status--error" role="status">${t('creator.chart.load_failed')}</div>`;
|
| 531 |
+
}
|
| 532 |
+
};
|
| 533 |
+
refresh();
|
| 534 |
+
if (!current(token) || chartInstance !== instance) return false;
|
| 535 |
+
const observer = new MutationObserver(refresh);
|
| 536 |
+
for (let ancestor = chartDom.parentElement; ancestor; ancestor = ancestor.parentElement) {
|
| 537 |
+
observer.observe(ancestor, { attributes: true, attributeFilter: ['class', 'style', 'data-theme', 'data-color-scheme'] });
|
| 538 |
+
}
|
| 539 |
+
const queries = ['(prefers-color-scheme: dark)', '(forced-colors: active)', '(prefers-reduced-motion: reduce)'].map(query => window.matchMedia(query));
|
| 540 |
+
for (const query of queries) query.addEventListener('change', refresh);
|
| 541 |
+
window.addEventListener('comfy-settings-changed', refresh);
|
| 542 |
+
removeChartAppearance = () => {
|
| 543 |
+
observer.disconnect();
|
| 544 |
+
for (const query of queries) query.removeEventListener('change', refresh);
|
| 545 |
+
window.removeEventListener('comfy-settings-changed', refresh);
|
| 546 |
+
};
|
| 547 |
+
return true;
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
function _initTrendChart(chartDom, trendData, token) {
|
| 551 |
if (!current(token)) return;
|
| 552 |
loadECharts().then(echarts => {
|
| 553 |
if (!current(token) || !chartDom.isConnected) return;
|
| 554 |
chartDom.innerHTML = "";
|
|
|
|
|
|
|
|
|
|
| 555 |
chartInstance = echarts.init(chartDom, null, { backgroundColor: 'transparent' });
|
| 556 |
+
isChartRendered = bindChartAppearance(chartDom, trendData, token);
|
| 557 |
+
if (!isChartRendered) return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
resizeTimer = setTimeout(() => {
|
| 559 |
resizeTimer = null;
|
| 560 |
if (current(token) && chartInstance) chartInstance.resize();
|
| 561 |
}, 300);
|
| 562 |
}).catch(() => {
|
| 563 |
if (!current(token)) return;
|
| 564 |
+
clearChartWork();
|
| 565 |
chartInstance?.dispose(); chartInstance = null; isChartRendered = false;
|
| 566 |
chartDom.innerHTML = `<div class="creator-card__chart-status creator-card__chart-status--error" role="status">${t('creator.chart.load_failed')}</div>`;
|
| 567 |
}).finally(() => { if (current(token)) chartPending = false; });
|
|
|
|
| 570 |
function renderTrendChart(trendData) {
|
| 571 |
const token = generation;
|
| 572 |
if (!current(token)) return;
|
| 573 |
+
const chartDom = detailView.querySelector(`#${chartContainerId}`);
|
| 574 |
+
if (!chartDom) return;
|
| 575 |
+
if (isChartRendered) { bindChartAppearance(chartDom, trendData, token); return; }
|
| 576 |
if (chartPending) return;
|
| 577 |
chartPending = true;
|
|
|
|
|
|
|
| 578 |
|
| 579 |
chartDom.innerHTML = `<div class="creator-card__chart-status" role="status">${t('creator.chart.loading')}</div>`;
|
| 580 |
|
前端页面/profile/个人中心视图.js
CHANGED
|
@@ -141,12 +141,19 @@ export function showUserProfile(initialUserData, currentUser = null, isMe = true
|
|
| 141 |
observer.observe(document.body, { childList: true, subtree: true });
|
| 142 |
scope.addCleanup(() => { observer.disconnect(); releaseChild(); });
|
| 143 |
|
| 144 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
if (!scope.isCurrent()) return;
|
| 146 |
const focused = document.activeElement;
|
| 147 |
const ownedFocus = document.hasFocus() && container.contains(focused);
|
| 148 |
const focusedTab = ownedFocus && focused.matches('.profile-tab-btn') ? focused.dataset.id : null;
|
| 149 |
-
const
|
|
|
|
| 150 |
releaseChild();
|
| 151 |
const generation = ++renderGeneration;
|
| 152 |
const current = () => scope.isCurrent() && renderGeneration === generation;
|
|
@@ -175,20 +182,34 @@ export function showUserProfile(initialUserData, currentUser = null, isMe = true
|
|
| 175 |
container.innerHTML = buildProfileHTML(userData, isMe, isSettingsView, isFollowing, followingCount, activeTab, tabs);
|
| 176 |
|
| 177 |
if (isSettingsView && isMe) {
|
|
|
|
| 178 |
const settingsContainer = createSettingsForm(
|
| 179 |
userData,
|
| 180 |
-
() => { if (!current()) return; isSettingsView = false; render(); },
|
| 181 |
(newUserData) => {
|
| 182 |
if (!current()) return;
|
| 183 |
userData = normalizeProfileData(newUserData, userData.account, true);
|
| 184 |
if (container.updateData) container.updateData(userData);
|
| 185 |
isSettingsView = false;
|
| 186 |
-
render();
|
| 187 |
},
|
| 188 |
{ isCurrent: current }
|
| 189 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
container.appendChild(settingsContainer);
|
| 191 |
-
container.querySelector("#btn-back-profile").onclick = () => { isSettingsView = false; render(); };
|
| 192 |
} else {
|
| 193 |
container.querySelector("#btn-back-profile").onclick = () => window.dispatchEvent(new CustomEvent("comfy-route-back"));
|
| 194 |
|
|
@@ -218,7 +239,7 @@ export function showUserProfile(initialUserData, currentUser = null, isMe = true
|
|
| 218 |
});
|
| 219 |
};
|
| 220 |
|
| 221 |
-
container.querySelector("#btn-open-settings").onclick = () => { isSettingsView = true; render(); };
|
| 222 |
container.querySelector("#btn-logout").onclick = async () => {
|
| 223 |
if (!current() || logoutPending) return;
|
| 224 |
const currentAccount = getCurrentAccount();
|
|
@@ -253,6 +274,7 @@ export function showUserProfile(initialUserData, currentUser = null, isMe = true
|
|
| 253 |
btnFollow.onclick = async () => {
|
| 254 |
if (!current()) return;
|
| 255 |
if (!currentUser) return showToast(t('feedback.login_required'), "warning");
|
|
|
|
| 256 |
btnFollow.innerHTML = `⏳ ${t('common.processing')}...`; btnFollow.disabled = true;
|
| 257 |
try {
|
| 258 |
const newStatus = !isFollowing;
|
|
@@ -264,9 +286,15 @@ export function showUserProfile(initialUserData, currentUser = null, isMe = true
|
|
| 264 |
if (!userData.followers) userData.followers = [];
|
| 265 |
if (isFollowing) { if (!userData.followers.includes(currentUser.account)) userData.followers.push(currentUser.account); }
|
| 266 |
else { userData.followers = userData.followers.filter(a => a !== currentUser.account); }
|
| 267 |
-
|
|
|
|
| 268 |
} catch (_) { if (!current()) return; showToast(t('feedback.operation_failed'), "error"); btnFollow.innerHTML = isFollowing ? `✔️ ${t('social.followed')}` : `➕ ${t('social.follow')}`; }
|
| 269 |
-
finally {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
};
|
| 271 |
}
|
| 272 |
|
|
@@ -299,12 +327,11 @@ export function showUserProfile(initialUserData, currentUser = null, isMe = true
|
|
| 299 |
listDOM._profileAddCleanup = cleanup => scope.addCleanup(cleanup);
|
| 300 |
renderProfileListContent(activeTab, listDOM, userData, currentUser, openOtherUserProfileModal);
|
| 301 |
}
|
| 302 |
-
if (
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
? [...container.querySelectorAll('.profile-
|
| 306 |
-
|
| 307 |
-
if (target && !target.disabled && target.getClientRects().length) target.focus({ preventScroll: true });
|
| 308 |
}
|
| 309 |
}
|
| 310 |
|
|
|
|
| 141 |
observer.observe(document.body, { childList: true, subtree: true });
|
| 142 |
scope.addCleanup(() => { observer.disconnect(); releaseChild(); });
|
| 143 |
|
| 144 |
+
function restoreOwnedFocus(target, owned) {
|
| 145 |
+
if (owned && scope.isCurrent() && container.isConnected && document.hasFocus()
|
| 146 |
+
&& document.activeElement === document.body && target?.isConnected
|
| 147 |
+
&& !target.disabled && target.getClientRects().length) target.focus({ preventScroll: true });
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
function render(focusTarget = null, allowBodyFocus = false) {
|
| 151 |
if (!scope.isCurrent()) return;
|
| 152 |
const focused = document.activeElement;
|
| 153 |
const ownedFocus = document.hasFocus() && container.contains(focused);
|
| 154 |
const focusedTab = ownedFocus && focused.matches('.profile-tab-btn') ? focused.dataset.id : null;
|
| 155 |
+
const focusedControl = ownedFocus && focused.matches('.profile-header button[id]') ? focused.id : null;
|
| 156 |
+
const actionFocus = focusTarget && (ownedFocus || (allowBodyFocus && focused === document.body));
|
| 157 |
releaseChild();
|
| 158 |
const generation = ++renderGeneration;
|
| 159 |
const current = () => scope.isCurrent() && renderGeneration === generation;
|
|
|
|
| 182 |
container.innerHTML = buildProfileHTML(userData, isMe, isSettingsView, isFollowing, followingCount, activeTab, tabs);
|
| 183 |
|
| 184 |
if (isSettingsView && isMe) {
|
| 185 |
+
let settingsSubmitFocused = false;
|
| 186 |
const settingsContainer = createSettingsForm(
|
| 187 |
userData,
|
| 188 |
+
() => { if (!current()) return; isSettingsView = false; render('btn-open-settings'); },
|
| 189 |
(newUserData) => {
|
| 190 |
if (!current()) return;
|
| 191 |
userData = normalizeProfileData(newUserData, userData.account, true);
|
| 192 |
if (container.updateData) container.updateData(userData);
|
| 193 |
isSettingsView = false;
|
| 194 |
+
render('btn-open-settings', settingsSubmitFocused);
|
| 195 |
},
|
| 196 |
{ isCurrent: current }
|
| 197 |
);
|
| 198 |
+
const captureSettingsSubmitFocus = event => {
|
| 199 |
+
if (!current() || !settingsContainer.isConnected) return;
|
| 200 |
+
const saveButton = settingsContainer.querySelector('#btn-save-settings');
|
| 201 |
+
if (saveButton?.contains(event.target)) {
|
| 202 |
+
settingsSubmitFocused = document.hasFocus() && settingsContainer.contains(document.activeElement);
|
| 203 |
+
}
|
| 204 |
+
};
|
| 205 |
+
settingsContainer.addEventListener('click', captureSettingsSubmitFocus, true);
|
| 206 |
+
const cleanupSettings = settingsContainer._cleanup;
|
| 207 |
+
settingsContainer._cleanup = () => {
|
| 208 |
+
settingsContainer.removeEventListener('click', captureSettingsSubmitFocus, true);
|
| 209 |
+
cleanupSettings();
|
| 210 |
+
};
|
| 211 |
container.appendChild(settingsContainer);
|
| 212 |
+
container.querySelector("#btn-back-profile").onclick = () => { isSettingsView = false; render('btn-open-settings'); };
|
| 213 |
} else {
|
| 214 |
container.querySelector("#btn-back-profile").onclick = () => window.dispatchEvent(new CustomEvent("comfy-route-back"));
|
| 215 |
|
|
|
|
| 239 |
});
|
| 240 |
};
|
| 241 |
|
| 242 |
+
container.querySelector("#btn-open-settings").onclick = () => { isSettingsView = true; render('setting-name'); };
|
| 243 |
container.querySelector("#btn-logout").onclick = async () => {
|
| 244 |
if (!current() || logoutPending) return;
|
| 245 |
const currentAccount = getCurrentAccount();
|
|
|
|
| 274 |
btnFollow.onclick = async () => {
|
| 275 |
if (!current()) return;
|
| 276 |
if (!currentUser) return showToast(t('feedback.login_required'), "warning");
|
| 277 |
+
const followFocusOwned = document.hasFocus() && document.activeElement === btnFollow;
|
| 278 |
btnFollow.innerHTML = `⏳ ${t('common.processing')}...`; btnFollow.disabled = true;
|
| 279 |
try {
|
| 280 |
const newStatus = !isFollowing;
|
|
|
|
| 286 |
if (!userData.followers) userData.followers = [];
|
| 287 |
if (isFollowing) { if (!userData.followers.includes(currentUser.account)) userData.followers.push(currentUser.account); }
|
| 288 |
else { userData.followers = userData.followers.filter(a => a !== currentUser.account); }
|
| 289 |
+
const restoreFollow = followFocusOwned && [document.body, btnFollow].includes(document.activeElement);
|
| 290 |
+
render(restoreFollow ? 'btn-follow-user' : null, restoreFollow);
|
| 291 |
} catch (_) { if (!current()) return; showToast(t('feedback.operation_failed'), "error"); btnFollow.innerHTML = isFollowing ? `✔️ ${t('social.followed')}` : `➕ ${t('social.follow')}`; }
|
| 292 |
+
finally {
|
| 293 |
+
if (current()) {
|
| 294 |
+
btnFollow.disabled = false;
|
| 295 |
+
restoreOwnedFocus(btnFollow, followFocusOwned);
|
| 296 |
+
}
|
| 297 |
+
}
|
| 298 |
};
|
| 299 |
}
|
| 300 |
|
|
|
|
| 327 |
listDOM._profileAddCleanup = cleanup => scope.addCleanup(cleanup);
|
| 328 |
renderProfileListContent(activeTab, listDOM, userData, currentUser, openOtherUserProfileModal);
|
| 329 |
}
|
| 330 |
+
if (current()) {
|
| 331 |
+
const target = actionFocus ? container.querySelector('#' + focusTarget)
|
| 332 |
+
: focusedTab ? [...container.querySelectorAll('.profile-tab-btn')].find(button => button.dataset.id === focusedTab)
|
| 333 |
+
: focusedControl ? [...container.querySelectorAll('.profile-header button[id]')].find(button => button.id === focusedControl) : null;
|
| 334 |
+
restoreOwnedFocus(target, actionFocus || focusedTab || focusedControl);
|
|
|
|
| 335 |
}
|
| 336 |
}
|
| 337 |
|
前端页面/styles/主题基础.css
CHANGED
|
@@ -353,15 +353,18 @@
|
|
| 353 |
.comfy-ranking-root .top-nav button:focus-visible { outline: 2px solid var(--rk-focus); outline-offset: 2px; }
|
| 354 |
@media (hover: hover) { .comfy-ranking-root .top-nav__icon:hover, .comfy-ranking-root .top-nav__identity:hover { border-color: var(--rk-focus); } .comfy-ranking-root .top-nav__brand:hover { opacity: .85; } }
|
| 355 |
|
|
|
|
|
|
|
| 356 |
.comfy-ranking-root .top-banner {
|
| 357 |
display: flex;
|
| 358 |
flex: 0 0 100%;
|
| 359 |
width: 100%;
|
| 360 |
min-width: 0;
|
| 361 |
-
|
|
|
|
| 362 |
justify-content: center;
|
| 363 |
align-items: center;
|
| 364 |
-
margin: 0;
|
| 365 |
padding: 0;
|
| 366 |
overflow: hidden;
|
| 367 |
border: 0;
|
|
@@ -377,10 +380,10 @@
|
|
| 377 |
}
|
| 378 |
|
| 379 |
.comfy-ranking-root .top-banner__image {
|
| 380 |
-
max-width: 100%;
|
| 381 |
width: auto;
|
| 382 |
height: auto;
|
| 383 |
-
max-height: 64px;
|
| 384 |
display: block;
|
| 385 |
object-fit: contain;
|
| 386 |
}
|
|
@@ -1581,7 +1584,7 @@ button.ranking-rating__star { cursor: pointer; min-width: 28px; min-height: 28px
|
|
| 1581 |
.creator-card__detail[hidden] { display:none !important; }
|
| 1582 |
.creator-card__section-label { font-size:12px; font-weight:700; margin-bottom:8px; }
|
| 1583 |
.creator-card__section-label--comments { margin-top:16px; }
|
| 1584 |
-
.creator-card__chart { width:100%; height:180px; background:var(--rk-bg,var(--comfy-menu-bg,#202020)); border-radius:6px; margin-bottom:12px; }
|
| 1585 |
.creator-card__comments { height:250px; border:1px solid var(--rk-border,#454545); border-radius:6px; overflow:hidden; }
|
| 1586 |
.creator-card__status { padding:32px 12px; text-align:center; color:var(--rk-muted,var(--descrip-text,#a4a4a4)); }
|
| 1587 |
.creator-card__status--error, .creator-card__chart-status--error { color:var(--rk-danger,#ef5350); }
|
|
|
|
| 353 |
.comfy-ranking-root .top-nav button:focus-visible { outline: 2px solid var(--rk-focus); outline-offset: 2px; }
|
| 354 |
@media (hover: hover) { .comfy-ranking-root .top-nav__icon:hover, .comfy-ranking-root .top-nav__identity:hover { border-color: var(--rk-focus); } .comfy-ranking-root .top-nav__brand:hover { opacity: .85; } }
|
| 355 |
|
| 356 |
+
.comfy-ranking-root .top-nav:has(> .top-banner > .top-banner__image) { padding-bottom: 0; }
|
| 357 |
+
|
| 358 |
.comfy-ranking-root .top-banner {
|
| 359 |
display: flex;
|
| 360 |
flex: 0 0 100%;
|
| 361 |
width: 100%;
|
| 362 |
min-width: 0;
|
| 363 |
+
min-height: 0;
|
| 364 |
+
max-height: calc(64px * 2 / 3);
|
| 365 |
justify-content: center;
|
| 366 |
align-items: center;
|
| 367 |
+
margin: -8px 0 0;
|
| 368 |
padding: 0;
|
| 369 |
overflow: hidden;
|
| 370 |
border: 0;
|
|
|
|
| 380 |
}
|
| 381 |
|
| 382 |
.comfy-ranking-root .top-banner__image {
|
| 383 |
+
max-width: calc(100% * 2 / 3);
|
| 384 |
width: auto;
|
| 385 |
height: auto;
|
| 386 |
+
max-height: calc(64px * 2 / 3);
|
| 387 |
display: block;
|
| 388 |
object-fit: contain;
|
| 389 |
}
|
|
|
|
| 1584 |
.creator-card__detail[hidden] { display:none !important; }
|
| 1585 |
.creator-card__section-label { font-size:12px; font-weight:700; margin-bottom:8px; }
|
| 1586 |
.creator-card__section-label--comments { margin-top:16px; }
|
| 1587 |
+
.creator-card__chart { width:100%; height:max(180px,11.25rem); font-size:.75rem; background:var(--rk-bg,var(--comfy-menu-bg,#202020)); border-radius:6px; margin-bottom:12px; }
|
| 1588 |
.creator-card__comments { height:250px; border:1px solid var(--rk-border,#454545); border-radius:6px; overflow:hidden; }
|
| 1589 |
.creator-card__status { padding:32px 12px; text-align:center; color:var(--rk-muted,var(--descrip-text,#a4a4a4)); }
|
| 1590 |
.creator-card__status--error, .creator-card__chart-status--error { color:var(--rk-danger,#ef5350); }
|