NewHome/assets/js/common.js

309 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* common.js —— 全站通用交互
* 功能:昼/夜主题切换、实时时钟、随机名言刷新、四大板块 hover 浮层、
* 搜索栏(引擎下拉/清除/回车搜索、复制到剪贴板、DOM 工具函数
*/
(function (global) {
'use strict';
/* ---------- 工具 ---------- */
function $(sel, root) { return (root || document).querySelector(sel); }
function $$(sel, root) { return Array.prototype.slice.call((root || document).querySelectorAll(sel)); }
/* ---------- 主题(昼/夜) ---------- */
var THEME_KEY = 'hp-theme';
function currentTheme() {
var saved = null;
try { saved = localStorage.getItem(THEME_KEY); } catch (e) { /* ignore */ }
if (saved === 'light' || saved === 'dark') return saved;
// 未设置:跟随系统
return (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light';
}
function applyTheme(t, persist) {
document.documentElement.setAttribute('data-theme', t);
if (persist) {
try { localStorage.setItem(THEME_KEY, t); } catch (e) { /* ignore */ }
}
var icons = $$('.js-theme-icon');
icons.forEach(function (el) { el.textContent = (t === 'dark') ? '☀' : '☾'; });
var btn = $('.js-theme-btn');
if (btn) btn.title = (t === 'dark') ? '切换到白天模式' : '切换到黑夜模式';
}
function initTheme() {
applyTheme(currentTheme(), false);
$$('.js-theme-btn').forEach(function (btn) {
btn.addEventListener('click', function () {
var next = (document.documentElement.getAttribute('data-theme') === 'dark') ? 'light' : 'dark';
applyTheme(next, true);
});
});
if (window.matchMedia) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
// 用户手动选过则不跟随系统变化
try {
if (!localStorage.getItem(THEME_KEY)) applyTheme(currentTheme(), false);
} catch (e) { applyTheme(currentTheme(), false); }
});
}
}
/* ---------- 实时时钟 ---------- */
function pad(n) { return (n < 10 ? '0' : '') + n; }
function formatTime(d) {
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' +
pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
}
function initClocks() {
function tick() { $$('.js-clock').forEach(function (el) { el.textContent = formatTime(new Date()); }); }
tick();
setInterval(tick, 1000);
}
/* ---------- 名言条:点击格言本身切换 ---------- */
function initQuoteRefresh() {
$$('.js-quote').forEach(function (quote) {
quote.addEventListener('click', function () {
var list = $$('.js-quote-pool span', document); // 服务端把所有名言放入隐藏池
if (!list.length) return;
// 定位当前展示文本在池中的位置,避免换到下一条仍是同一条
var currentText = quote.textContent;
var cur = -1;
for (var i = 0; i < list.length; i++) {
if (list[i].textContent === currentText) { cur = i; break; }
}
var n = list.length;
var idx = Math.floor(Math.random() * n);
if (n > 1 && cur !== -1) {
while (idx === cur) idx = (idx + 1) % n;
}
quote.textContent = list[idx].textContent;
});
});
}
/* ---------- 首页四大板块 hover 展开说明 ---------- */
function initBoardHover() {
var section = $('.js-boards');
if (!section) return;
var cards = $$('.js-board-card', section);
var overlay = $('.js-board-panel', section);
if (!cards.length || !overlay) return;
var titleEl = $('.js-board-panel-title', overlay);
var descEl = $('.js-board-panel-desc', overlay);
var bodyEl = section;
function showPanel(card) {
var name = card.getAttribute('data-name') || '';
var desc = card.getAttribute('data-desc') || '';
var idx = cards.indexOf(card);
if (titleEl) titleEl.textContent = name;
if (descEl) descEl.textContent = desc;
overlay.classList.remove('panel-left', 'panel-right');
// 第 0 / 1 块吸附左侧边缘,第 2 / 3 块吸附右侧边缘(实际板块位置不变)
if (idx === 0 || idx === 1) overlay.classList.add('panel-left');
else overlay.classList.add('panel-right');
overlay.classList.add('visible');
}
cards.forEach(function (card) {
card.addEventListener('mouseenter', function () { showPanel(card); });
card.addEventListener('click', function () { /* 点击由 <a> 包裹,这里无额外处理 */ });
});
section.addEventListener('mouseleave', function () { overlay.classList.remove('visible'); });
}
/* ---------- 搜索栏 ---------- */
var DEFAULT_ENGINES = [
{ key: 'bing', name: 'Bing', icon: 'B', url: 'https://www.bing.com/search?q={kw}' },
{ key: 'baidu', name: '百度', icon: '度', url: 'https://www.baidu.com/s?wd={kw}' },
{ key: 'github', name: 'GitHub', icon: 'G', url: 'https://github.com/search?q={kw}' },
{ key: 'google', name: 'Google', icon: 'G', url: 'https://www.google.com/search?q={kw}' }
];
function initSearch() {
var box = $('.js-search');
if (!box) return;
var cfg = null;
try { cfg = JSON.parse(box.getAttribute('data-engines') || 'null'); } catch (e) { cfg = null; }
var engines = (cfg && cfg.length) ? cfg : DEFAULT_ENGINES;
var curKey = box.getAttribute('data-engine') || engines[0].key;
var cur = engines.filter(function (e) { return e.key === curKey; })[0] || engines[0];
var input = $('.js-search-input', box);
var clearBtn = $('.js-search-clear', box);
var engineBtn = $('.js-engine-btn', box);
var engineName = $('.js-engine-name', box);
var menu = $('.js-engine-menu', box);
function setEngine(e) {
cur = e;
// 按钮区域只显示图标(全称在展开下拉列表时展示)
if (engineName) engineName.textContent = e.icon || '';
if (engineBtn) engineBtn.title = '搜索引擎:' + e.name;
box.setAttribute('data-engine', e.key);
hideMenu();
}
function showMenu() {
if (!menu) return;
if (!menu.children.length) {
engines.forEach(function (e) {
var li = document.createElement('li');
li.textContent = e.icon + ' ' + e.name;
li.className = (e.key === cur.key) ? 'active' : '';
li.addEventListener('click', function () { setEngine(e); });
menu.appendChild(li);
});
}
menu.classList.add('open');
}
function hideMenu() { if (menu) menu.classList.remove('open'); }
function updateClear() {
if (clearBtn) clearBtn.style.visibility = (input && input.value) ? 'visible' : 'hidden';
}
function doSearch() {
var kw = (input ? input.value : '').trim();
if (!kw) { if (input) input.focus(); return; }
window.open(cur.url.replace('{kw}', encodeURIComponent(kw)), '_blank', 'noopener');
}
if (engineBtn) engineBtn.addEventListener('click', function (ev) {
ev.stopPropagation();
if (menu && menu.classList.contains('open')) hideMenu(); else showMenu();
});
if (clearBtn) clearBtn.addEventListener('click', function () {
if (input) { input.value = ''; input.focus(); }
updateClear();
});
if (input) {
input.addEventListener('input', updateClear);
input.addEventListener('keydown', function (ev) { if (ev.key === 'Enter') doSearch(); });
}
document.addEventListener('click', function () { hideMenu(); });
var goBtn = $('.js-search-go', box);
if (goBtn) goBtn.addEventListener('click', doSearch);
setEngine(cur);
updateClear();
}
/* ---------- 后台图标控件:文本 / 图片上传≤200KB → dataURL ---------- */
function isIconImage(v) {
v = String(v || '').trim();
if (!v) return false;
if (/^data:image\//i.test(v) || /^https?:\/\//i.test(v)) return true;
if (/\.(png|jpe?g|gif|svg|webp)(\?|#|$)/i.test(v)) return true;
return v.charAt(0) === '/' || v.indexOf('./') === 0 || v.indexOf('../') === 0;
}
function updateIconPreview(ctl) {
var input = ctl.querySelector('.icon-input');
var box = ctl.querySelector('.icon-preview');
if (!input || !box) return;
var v = input.value.trim();
box.innerHTML = '';
if (!v) return; // 空:显示空心占位框
if (isIconImage(v)) {
var img = document.createElement('img');
img.src = v;
img.alt = '';
img.loading = 'lazy';
box.appendChild(img);
} else {
box.textContent = v;
}
}
function initIconControls() {
// 点击“上传”→ 触发隐藏 file
document.addEventListener('click', function (ev) {
var btn = ev.target && ev.target.closest ? ev.target.closest('.icon-upload') : null;
if (!btn) return;
var ctl = btn.closest('.icon-ctl');
var file = ctl ? ctl.querySelector('.icon-file') : null;
if (file) file.click();
});
// 选择图片 → 校验并转 dataURL 写入同控件文本框
document.addEventListener('change', function (ev) {
var file = ev.target;
if (!file || !file.classList || !file.classList.contains('icon-file')) return;
var ctl = file.closest('.icon-ctl');
var input = ctl ? ctl.querySelector('.icon-input') : null;
var f = file.files && file.files[0];
function reset() { file.value = ''; }
if (!f) return;
if (f.type.indexOf('image/') !== 0) { alert('请选择图片文件。'); reset(); return; }
if (f.size > 200 * 1024) { alert('图片不能超过 200KB。'); reset(); return; }
var rd = new FileReader();
rd.onload = function () {
if (input) {
input.value = rd.result;
updateIconPreview(ctl);
}
reset();
};
rd.onerror = function () { alert('图片读取失败,请重试。'); reset(); };
rd.readAsDataURL(f);
});
// 手动输入文本 → 实时预览
document.addEventListener('input', function (ev) {
var t = ev.target;
if (t && t.classList && t.classList.contains('icon-input')) {
updateIconPreview(t.closest('.icon-ctl'));
}
});
// 初始化页面既有控件预览
$$('.icon-ctl').forEach(updateIconPreview);
}
/* ---------- 复制 ---------- */
function copyText(text, btnEl) {
function done(ok) {
if (!btnEl) return;
var old = btnEl.textContent;
btnEl.textContent = ok ? '✓ 已复制' : '复制失败';
setTimeout(function () { btnEl.textContent = old; }, 1500);
}
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function () { done(true); }, function () { fallbackCopy(text, done); });
} else {
fallbackCopy(text, done);
}
}
function fallbackCopy(text, done) {
var ta = document.createElement('textarea');
ta.value = text;
ta.style.cssText = 'position:fixed;opacity:0;';
document.body.appendChild(ta);
ta.select();
try { done(document.execCommand('copy')); } catch (e) { done(false); }
document.body.removeChild(ta);
}
function initCopyButtons() {
document.addEventListener('click', function (ev) {
var btn = ev.target.closest ? ev.target.closest('.js-copy') : null;
if (!btn) return;
var text = btn.getAttribute('data-copy');
if (text === null || text === undefined) {
var target = $(btn.getAttribute('data-target') || '');
text = target ? target.value : '';
}
copyText(text, btn);
});
}
/* ---------- 自动初始化 ---------- */
document.addEventListener('DOMContentLoaded', function () {
initTheme();
initClocks();
initQuoteRefresh();
initBoardHover();
initSearch();
initIconControls();
initCopyButtons();
});
global.HomePage = {
$: $, $$: $$, copyText: copyText, applyTheme: applyTheme, currentTheme: currentTheme
};
})(window);