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({
|
localStorage.setItem(cacheKey, JSON.stringify({
|
||||||
value: theme, time: new Date().getTime()
|
value: theme, time: new Date().getTime()
|
||||||
}));
|
}));
|
||||||
console.log("已保存主题设置:", theme);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 公共方法:获取本地存储的主题设置
|
// 公共方法:获取本地存储的主题设置
|
||||||
@@ -33,7 +32,6 @@ function getStoredTheme() {
|
|||||||
const night = hour >= 18 || prefersDark;
|
const night = hour >= 18 || prefersDark;
|
||||||
theme = night ? 'night' : 'day';
|
theme = night ? 'night' : 'day';
|
||||||
setStoredTheme(theme)
|
setStoredTheme(theme)
|
||||||
console.log("已初始化主题设置:", theme);
|
|
||||||
} else if (saved.value) {
|
} else if (saved.value) {
|
||||||
theme = saved.value;
|
theme = saved.value;
|
||||||
}
|
}
|
||||||
@@ -41,10 +39,16 @@ function getStoredTheme() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
// 启动应用核心
|
|
||||||
const app = new AppCore();
|
const app = new AppCore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function escapeHtml(s) {
|
||||||
|
if (s == null) return '';
|
||||||
|
return String(s).replace(/[&<>\"]/g, function (c) {
|
||||||
|
return ({'&': '&', '<': '<', '>': '>', '"': '"'})[c];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
class AppCore {
|
class AppCore {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.i18n = new I18nManager();
|
this.i18n = new I18nManager();
|
||||||
@@ -59,7 +63,7 @@ class AppCore {
|
|||||||
=========================== */
|
=========================== */
|
||||||
class I18nManager {
|
class I18nManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.lang = localStorage.getItem('lang') || (navigator.language.startsWith('zh') ? 'zh' : 'en');
|
this.lang = getStoredLanguage();
|
||||||
this.dict = {
|
this.dict = {
|
||||||
zh: {
|
zh: {
|
||||||
"nav.home": "首页",
|
"nav.home": "首页",
|
||||||
@@ -282,20 +286,20 @@ class DataManager {
|
|||||||
// Fix: API field compatibility
|
// Fix: API field compatibility
|
||||||
const stars = repo.stargazers_count !== undefined ? repo.stargazers_count : (repo.stars || 0);
|
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 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 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 += `
|
html += `
|
||||||
<div class="repo-card" onclick="window.open('${url}')">
|
<div class="repo-card" onclick="window.open('${url}', '_blank', 'noopener,noreferrer')">
|
||||||
<div class="repo-head">
|
<div class="repo-head">
|
||||||
<span class="gradient-text">${repo.name}</span>
|
<span class="gradient-text">${escapeHtml(repo.name)}</span>
|
||||||
<span>
|
<span>
|
||||||
<i class="ri-star-fill"></i> ${stars}
|
<i class="ri-star-fill"></i> ${stars}
|
||||||
<i class="ri-git-branch-fill"></i> ${forks}
|
<i class="ri-git-branch-fill"></i> ${forks}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="repo-desc">${dShort}</div>
|
<div class="repo-desc">${escapeHtml(dShort)}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
$('#projects-container').html(html);
|
$('#projects-container').html(html);
|
||||||
@@ -371,7 +375,7 @@ class DataManager {
|
|||||||
|
|
||||||
renderBlog(list) {
|
renderBlog(list) {
|
||||||
let html = '';
|
let html = '';
|
||||||
const lang = localStorage.getItem('lang') || (navigator.language && navigator.language.startsWith('zh') ? 'zh' : 'en');
|
const lang = getStoredLanguage();
|
||||||
const fmtDate = (dStr) => {
|
const fmtDate = (dStr) => {
|
||||||
if (!dStr) return '';
|
if (!dStr) return '';
|
||||||
const d = new Date(dStr);
|
const d = new Date(dStr);
|
||||||
@@ -398,12 +402,12 @@ class DataManager {
|
|||||||
const link = post.link || post.url || '#';
|
const link = post.link || post.url || '#';
|
||||||
|
|
||||||
html += `
|
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-info">
|
||||||
<div class="b-title">${post.title}</div>
|
<div class="b-title">${escapeHtml(post.title)}</div>
|
||||||
<div class="b-date">${date}</div>
|
<div class="b-date">${escapeHtml(date)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="b-cat">${cat}</div>
|
<div class="b-cat">${escapeHtml(cat)}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
$('#blog-container').html(html);
|
$('#blog-container').html(html);
|
||||||
@@ -459,11 +463,12 @@ class UIManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initArtalk() {
|
initArtalk() {
|
||||||
// Safe initialization
|
|
||||||
const isHttps = location.protocol === 'https:';
|
const isHttps = location.protocol === 'https:';
|
||||||
const isLocal = !!(window.SiteConfig?.dev?.isLocal);
|
const isLocal = !!(window.SiteConfig?.dev?.isLocal);
|
||||||
if (!isHttps || 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;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof Artalk !== 'undefined' && window.SiteConfig?.artalk) {
|
if (typeof Artalk !== 'undefined' && window.SiteConfig?.artalk) {
|
||||||
@@ -478,10 +483,14 @@ class UIManager {
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Artalk Error", 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 {
|
} 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', () => {
|
main.addEventListener('click', () => {
|
||||||
menu.classList.toggle('open');
|
menu.classList.toggle('open');
|
||||||
|
main.setAttribute('aria-expanded', menu.classList.contains('open') ? 'true' : 'false');
|
||||||
updateLabels();
|
updateLabels();
|
||||||
});
|
});
|
||||||
fLang.addEventListener('click', () => {
|
fLang.addEventListener('click', () => {
|
||||||
@@ -668,10 +678,7 @@ class UIManager {
|
|||||||
|
|
||||||
let isDragging = false;
|
let isDragging = false;
|
||||||
let initialX, initialY, currentX, currentY, xOffset = 0, yOffset = 0;
|
let initialX, initialY, currentX, currentY, xOffset = 0, yOffset = 0;
|
||||||
const windowWidth = window.innerWidth;
|
fab.style.willChange = 'transform';
|
||||||
const windowHeight = window.innerHeight;
|
|
||||||
const fabWidth = fab.offsetWidth;
|
|
||||||
const fabHeight = fab.offsetHeight;
|
|
||||||
|
|
||||||
// 拖拽相关方法
|
// 拖拽相关方法
|
||||||
const setTranslate = (xPos, yPos, el) => {
|
const setTranslate = (xPos, yPos, el) => {
|
||||||
@@ -706,9 +713,13 @@ class UIManager {
|
|||||||
currentY = e.clientY - initialY;
|
currentY = e.clientY - initialY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 限制在屏幕内
|
const ww = window.innerWidth;
|
||||||
currentX = Math.max(0, Math.min(currentX, windowWidth - fabWidth));
|
const wh = window.innerHeight;
|
||||||
currentY = Math.max(0, Math.min(currentY, windowHeight - fabHeight));
|
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;
|
xOffset = currentX;
|
||||||
yOffset = currentY;
|
yOffset = currentY;
|
||||||
|
|||||||
Reference in New Issue
Block a user