新增: - 书库导入导出(library_import/library_export)及测试 - 图片变体生成(image_variants)与响应式图片(responsiveImage) - 书籍搜索(bookSearch)+ BookSearch/LibrarySearchGrid 组件 - 小组件运行时(widgetRuntime)与静态检查(widgetLint) - 上传缓存中间件(upload_cache)与字体 CSS 提取脚本 其它: - 后端 handlers/services 全量调整 - 前端页面、组件、库函数与配置更新
55 lines
1.9 KiB
TypeScript
55 lines
1.9 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useLayoutEffect, useRef } from "react";
|
||
import type { SidebarWidgetPublic, User } from "@/lib/api";
|
||
import { mountWidget } from "@/lib/widgetRuntime";
|
||
import { ensureWidgetApi } from "@/lib/widgetApi";
|
||
|
||
// 站长自定义工具块:所有工具统一在 Shadow DOM 内渲染(attachShadow 是客户端能力,
|
||
// 因此服务端只输出空容器,HTML/CSS/JS 全部在客户端 effect 中注入,无水合不一致)。
|
||
// - CSS 注入 shadow 内,不污染全站,也不被全站样式污染;
|
||
// - JS 以函数形式执行,接收 (root, shadow, toolId, registerCleanup),
|
||
// DOM 查找从 root 出发,同页多个同款工具互不干扰;
|
||
// - unmount 时统一执行 cleanup 并清空 shadow。
|
||
// 站长可信内容,不做消毒/沙箱。
|
||
export default function CustomWidgetBlock({
|
||
widget,
|
||
viewer,
|
||
}: {
|
||
widget: SidebarWidgetPublic;
|
||
viewer?: User | null;
|
||
}) {
|
||
const hostRef = useRef<HTMLElement>(null);
|
||
|
||
// 服务端用 useEffect 避免告警,客户端用 layout effect 首帧前完成注入,避免闪烁
|
||
const useIsomorphicLayoutEffect =
|
||
typeof window !== "undefined" ? useLayoutEffect : useEffect;
|
||
|
||
// 两个 effect 同类型、按声明顺序执行:先建立站点接口,再挂载工具,
|
||
// 保证用户脚本首帧同步读取 window.J13 时接口已就绪。
|
||
useIsomorphicLayoutEffect(() => {
|
||
ensureWidgetApi(viewer);
|
||
}, [viewer]);
|
||
|
||
useIsomorphicLayoutEffect(() => {
|
||
const host = hostRef.current;
|
||
if (!host) return;
|
||
const handle = mountWidget(host, {
|
||
id: widget.id,
|
||
html: widget.html,
|
||
css: widget.css,
|
||
js: widget.js,
|
||
});
|
||
return () => handle.unmount();
|
||
}, [widget.id, widget.html, widget.css, widget.js]);
|
||
|
||
return (
|
||
<section
|
||
ref={hostRef}
|
||
className="j13-widget-body"
|
||
data-widget-id={widget.id}
|
||
data-widget-name={widget.name}
|
||
/>
|
||
);
|
||
}
|