Files
jiang13-bbs/frontend/lib/customCode.ts
freefire 5f7193042f feat: 外观支持自定义 CSS/JS,并升级发帖工作台与积分存量看板
后台可注入全站 CSS/JS(SSR 生效,CSS 可热换);发帖/编辑改为锁一屏工作室;经济看板增加存量健康度;主题令牌与弹层误关一并收紧。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-18 04:16:32 +08:00

101 lines
2.9 KiB
TypeScript
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.
// 全站自定义 CSS:SSR <style id="j13-custom-css"> + 客户端 #j13-custom-css-runtime。
// 禁止对 SSR 节点 remove/改写;预览时 disable SSR,由 runtime 作为唯一源。
export const MAX_CUSTOM_CSS_CHARS = 48000;
export const MAX_CUSTOM_JS_CHARS = 48000;
/** 空配置时展示的注释模板:保存前不生效,全部注释故误保存也无副作用 */
export const SAMPLE_CUSTOM_CSS = `/* 姜十三 · 自定义 CSS 示例
* 覆盖 globals.css 令牌或组件类。保存后对全站访客生效。
* 勿写入 </style>
*
* 常用令牌:
* --bg / --panel / --ink / --accent
* --bg-halo 顶部光晕色(建议 ≤ 0.04)
* --bg-noise-opacity 噪点层透明度
* --r / --shadow-sm 卡片圆角与阴影
*/
/* 改底色与光晕(浅 / 暗成对改)
:root {
--bg: #f7f7f5;
--bg-halo: rgba(7, 192, 95, 0.04);
--bg-noise-opacity: 0.03;
--panel: #ffffff;
--r: 12px;
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05);
}
.dark {
--bg: #101211;
--bg-halo: rgba(7, 192, 95, 0.03);
--bg-noise-opacity: 0.04;
}
*/
/* 关掉光晕和噪点
body::before,
body::after {
display: none;
}
*/
/* 隐藏页脚
footer { display: none; }
*/
`;
export const SAMPLE_CUSTOM_JS = `// 姜十三 · 自定义 JS 示例
// 仅整页加载执行一次(F5 / 首次进入),站内软导航不会重跑。
// 勿写入 </script>
//
// 给 <html> 打标记,便于 CSS 写 html[data-custom] .panel { ... }
// document.documentElement.dataset.custom = "1";
//
// 等 DOM 就绪后再改节点
// document.addEventListener("DOMContentLoaded", function () {
// // document.querySelector("footer")?.remove();
// });
`;
const SSR_STYLE_ID = "j13-custom-css";
const RUNTIME_STYLE_ID = "j13-custom-css-runtime";
export function clipCustomSnippet(raw: string, max: number): string {
const s = (raw || "").replace(/\u0000/g, "");
if (s.length <= max) return s;
return [...s].slice(0, max).join("");
}
function ensureRuntimeStyle(): HTMLStyleElement {
let el = document.getElementById(RUNTIME_STYLE_ID) as HTMLStyleElement | null;
if (el) return el;
el = document.createElement("style");
el.id = RUNTIME_STYLE_ID;
el.setAttribute("data-custom-css-source", "runtime");
document.body.appendChild(el);
return el;
}
function removeRuntimeStyle(): void {
document.getElementById(RUNTIME_STYLE_ID)?.remove();
}
function ssrStyle(): HTMLStyleElement | null {
return document.getElementById(SSR_STYLE_ID) as HTMLStyleElement | null;
}
/** 将自定义 CSS 应用到当前文档。preview=true 时忽略 SSR 节点(外观页草稿)。 */
export function applyCustomCSS(css: string, preview = false): void {
if (typeof document === "undefined") return;
const text = clipCustomSnippet(css, MAX_CUSTOM_CSS_CHARS).trim();
const ssr = ssrStyle();
if (ssr) ssr.disabled = preview;
if (!text) {
removeRuntimeStyle();
if (!preview && ssr) ssr.disabled = false;
return;
}
ensureRuntimeStyle().textContent = text;
}