feat(about): 增强安全性和可访问性
- 移除调试用的 console.log 输出 - 添加 escapeHtml 函数防止 XSS 攻击 - 使用 getStoredLanguage 统一语言获取逻辑 - 为外部链接添加 noopener 和 noreferrer 属性 - 在动态内容插入时使用 escapeHtml 进行转义 - 更新评论区关闭提示的多语言支持 - 为菜单按钮添加 aria-expanded 属性提升可访问性 - 优化拖拽功能的边界计算逻辑 - 设置 will-change 提升拖拽性能
This commit is contained in:
61
js/about.js
61
js/about.js
@@ -17,7 +17,6 @@ function setStoredTheme(theme) {
|
||||
localStorage.setItem(cacheKey, JSON.stringify({
|
||||
value: theme, time: new Date().getTime()
|
||||
}));
|
||||
console.log("已保存主题设置:", theme);
|
||||
}
|
||||
|
||||
// 公共方法:获取本地存储的主题设置
|
||||
@@ -33,7 +32,6 @@ function getStoredTheme() {
|
||||
const night = hour >= 18 || prefersDark;
|
||||
theme = night ? 'night' : 'day';
|
||||
setStoredTheme(theme)
|
||||
console.log("已初始化主题设置:", theme);
|
||||
} else if (saved.value) {
|
||||
theme = saved.value;
|
||||
}
|
||||
@@ -41,10 +39,16 @@ function getStoredTheme() {
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
// 启动应用核心
|
||||
const app = new AppCore();
|
||||
});
|
||||
|
||||
function escapeHtml(s) {
|
||||
if (s == null) return '';
|
||||
return String(s).replace(/[&<>\"]/g, function (c) {
|
||||
return ({'&': '&', '<': '<', '>': '>', '"': '"'})[c];
|
||||
});
|
||||
}
|
||||
|
||||
class AppCore {
|
||||
constructor() {
|
||||
this.i18n = new I18nManager();
|
||||
@@ -59,7 +63,7 @@ class AppCore {
|
||||
=========================== */
|
||||
class I18nManager {
|
||||
constructor() {
|
||||
this.lang = localStorage.getItem('lang') || (navigator.language.startsWith('zh') ? 'zh' : 'en');
|
||||
this.lang = getStoredLanguage();
|
||||
this.dict = {
|
||||
zh: {
|
||||
"nav.home": "首页",
|
||||
@@ -282,20 +286,20 @@ class DataManager {
|
||||
// Fix: API field compatibility
|
||||
const stars = repo.stargazers_count !== undefined ? repo.stargazers_count : (repo.stars || 0);
|
||||
const forks = repo.forks_count !== undefined ? repo.forks_count : (repo.forks || 0);
|
||||
const desc = repo.description || repo.desc || 'No description.';
|
||||
const descRaw = repo.description || repo.desc || 'No description.';
|
||||
const url = repo.html_url || repo.url || '#';
|
||||
const dShort = (desc || '').length > 120 ? (desc.slice(0, 117) + '...') : desc;
|
||||
const dShort = (descRaw || '').length > 120 ? (descRaw.slice(0, 117) + '...') : descRaw;
|
||||
|
||||
html += `
|
||||
<div class="repo-card" onclick="window.open('${url}')">
|
||||
<div class="repo-card" onclick="window.open('${url}', '_blank', 'noopener,noreferrer')">
|
||||
<div class="repo-head">
|
||||
<span class="gradient-text">${repo.name}</span>
|
||||
<span class="gradient-text">${escapeHtml(repo.name)}</span>
|
||||
<span>
|
||||
<i class="ri-star-fill"></i> ${stars}
|
||||
<i class="ri-git-branch-fill"></i> ${forks}
|
||||
</span>
|
||||
</div>
|
||||
<div class="repo-desc">${dShort}</div>
|
||||
<div class="repo-desc">${escapeHtml(dShort)}</div>
|
||||
</div>`;
|
||||
});
|
||||
$('#projects-container').html(html);
|
||||
@@ -371,7 +375,7 @@ class DataManager {
|
||||
|
||||
renderBlog(list) {
|
||||
let html = '';
|
||||
const lang = localStorage.getItem('lang') || (navigator.language && navigator.language.startsWith('zh') ? 'zh' : 'en');
|
||||
const lang = getStoredLanguage();
|
||||
const fmtDate = (dStr) => {
|
||||
if (!dStr) return '';
|
||||
const d = new Date(dStr);
|
||||
@@ -398,12 +402,12 @@ class DataManager {
|
||||
const link = post.link || post.url || '#';
|
||||
|
||||
html += `
|
||||
<div class="blog-item" onclick="window.open('${link}')">
|
||||
<div class="blog-item" onclick="window.open('${link}', '_blank', 'noopener,noreferrer')">
|
||||
<div class="b-info">
|
||||
<div class="b-title">${post.title}</div>
|
||||
<div class="b-date">${date}</div>
|
||||
<div class="b-title">${escapeHtml(post.title)}</div>
|
||||
<div class="b-date">${escapeHtml(date)}</div>
|
||||
</div>
|
||||
<div class="b-cat">${cat}</div>
|
||||
<div class="b-cat">${escapeHtml(cat)}</div>
|
||||
</div>`;
|
||||
});
|
||||
$('#blog-container').html(html);
|
||||
@@ -459,11 +463,12 @@ class UIManager {
|
||||
}
|
||||
|
||||
initArtalk() {
|
||||
// Safe initialization
|
||||
const isHttps = location.protocol === 'https:';
|
||||
const isLocal = !!(window.SiteConfig?.dev?.isLocal);
|
||||
if (!isHttps || isLocal) {
|
||||
$('#artalk-container').html(`<div style="text-align:center;color:#999;padding:20px;">${(new I18nManager()).dict[(localStorage.getItem('lang') || 'zh')]['comment.closed']}</div>`);
|
||||
const lang = getStoredLanguage();
|
||||
const msg = lang === 'zh' ? '当前评论区已关闭' : 'Comments are closed';
|
||||
$('#artalk-container').html(`<div style="text-align:center;color:#999;padding:20px;">${msg}</div>`);
|
||||
return;
|
||||
}
|
||||
if (typeof Artalk !== 'undefined' && window.SiteConfig?.artalk) {
|
||||
@@ -478,10 +483,14 @@ class UIManager {
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Artalk Error", e);
|
||||
$('#artalk-container').html(`<div style="text-align:center;color:#999;padding:20px;">${(new I18nManager()).dict[(localStorage.getItem('lang') || 'zh')]['comment.closed']}</div>`);
|
||||
const lang = getStoredLanguage();
|
||||
const msg = lang === 'zh' ? '当前评论区已关闭' : 'Comments are closed';
|
||||
$('#artalk-container').html(`<div style="text-align:center;color:#999;padding:20px;">${msg}</div>`);
|
||||
}
|
||||
} else {
|
||||
$('#artalk-container').html(`<div style="text-align:center;color:#999;padding:20px;">${(new I18nManager()).dict[(localStorage.getItem('lang') || 'zh')]['comment.closed']}</div>`);
|
||||
const lang = getStoredLanguage();
|
||||
const msg = lang === 'zh' ? '当前评论区已关闭' : 'Comments are closed';
|
||||
$('#artalk-container').html(`<div style="text-align:center;color:#999;padding:20px;">${msg}</div>`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,6 +642,7 @@ class UIManager {
|
||||
};
|
||||
main.addEventListener('click', () => {
|
||||
menu.classList.toggle('open');
|
||||
main.setAttribute('aria-expanded', menu.classList.contains('open') ? 'true' : 'false');
|
||||
updateLabels();
|
||||
});
|
||||
fLang.addEventListener('click', () => {
|
||||
@@ -668,10 +678,7 @@ class UIManager {
|
||||
|
||||
let isDragging = false;
|
||||
let initialX, initialY, currentX, currentY, xOffset = 0, yOffset = 0;
|
||||
const windowWidth = window.innerWidth;
|
||||
const windowHeight = window.innerHeight;
|
||||
const fabWidth = fab.offsetWidth;
|
||||
const fabHeight = fab.offsetHeight;
|
||||
fab.style.willChange = 'transform';
|
||||
|
||||
// 拖拽相关方法
|
||||
const setTranslate = (xPos, yPos, el) => {
|
||||
@@ -706,9 +713,13 @@ class UIManager {
|
||||
currentY = e.clientY - initialY;
|
||||
}
|
||||
|
||||
// 限制在屏幕内
|
||||
currentX = Math.max(0, Math.min(currentX, windowWidth - fabWidth));
|
||||
currentY = Math.max(0, Math.min(currentY, windowHeight - fabHeight));
|
||||
const ww = window.innerWidth;
|
||||
const wh = window.innerHeight;
|
||||
const rect = fab.getBoundingClientRect();
|
||||
const fw = rect.width;
|
||||
const fh = rect.height;
|
||||
currentX = Math.max(0, Math.min(currentX, ww - fw));
|
||||
currentY = Math.max(0, Math.min(currentY, wh - fh));
|
||||
|
||||
xOffset = currentX;
|
||||
yOffset = currentY;
|
||||
|
||||
Reference in New Issue
Block a user