原来的生成器只能配「背景 + 人物 + 道具」三件套,预览还是自己画的仿制页面 —— 跟真实页面不像,也没法给不同页面配不同内容。 机制层: - internal/context.pageScope() 按路径归类,输出 <html data-page-scope>: box / question / admin / profile / vote(首页与 /explore 不加载主题,不列) - /user/questions 单独归为 admin:题目列表信息密,和设置表单的装饰诉求不同 - base.css 装饰槽位 6 -> 10,背景 8 层 -> 12 层;主题按作用域覆盖时同样写满 10 个 - 新增 TestDecoSlotCount:逐文件逐声明检查 10 个值。少写一个不报错, 只会让图层与 size/position 错位(整页重复同一张图 / 人物跑到左上角),注释守不住 生成器: - 画板改成 iframe 加载 outputs/preview-*.html(真实模板渲染),配 serve.py 起本地服务 解决 file:// 下父页面读不到 iframe DOM 的问题 - 八个页面作用域(去掉不生效的首页/探索),每个可独立配背景与最多 10 层装饰 - 图层类型:图片 / 径向光斑 / 线性光;9 宫格锚点 + 像素偏移,可在画板上直接拖 - 位置统一写 calc(0%|50%|100% ± Npx):百分比是相对 (容器-图片), 不用 left/bottom 关键字混搭,避免三值语法非法导致整条声明作废 - 配色只填 6 个基础色,其余 4 个必需令牌按方向派生(surface-2 往 text 靠, 浅色变深、深色变亮,方向自动正确) 配套: - zz_preview_test.go 增加单题页 / 题目列表 / 投票页预览,作为生成器画板 - docs/theme-page-scope.md 记录作用域取值、选择器特异性与 10 槽位约束 - StaticVersion 20261020e(base.css 变了) 验证:生成器导出的 CSS 通过 TestThemeCSSComplete + TestDecoSlotCount; 真实箱主页计算样式为 12 层对齐;临时主题验证「question 覆盖为 none」生效、 未覆盖时回退全局。
70 lines
2.5 KiB
Python
70 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""主题生成器的本地预览服务。
|
|
|
|
为什么必须起服务:生成器不再自己画一个"仿制页面",而是把项目真实渲染出来的
|
|
页面(outputs/preview-*.html)放进 iframe 当画板,再往里注入正在编辑的主题 CSS。
|
|
file:// 下浏览器不允许父页面访问 iframe 的 DOM(即使同目录),预览就只能退回示意图。
|
|
起一个本地 HTTP 服务后父子同源,拖拽、注入、实时改令牌都能正常工作。
|
|
|
|
用法(在项目根目录执行任意一种):
|
|
|
|
python dev/theme-builder/serve.py
|
|
python -m http.server 8777
|
|
|
|
然后打开 http://localhost:8777/dev/theme-builder/
|
|
|
|
这个脚本只读文件,不写任何东西,也不改项目配置。
|
|
"""
|
|
|
|
import argparse
|
|
import functools
|
|
import http.server
|
|
import os
|
|
import socketserver
|
|
import sys
|
|
|
|
# dev/theme-builder/serve.py → 上两级就是项目根
|
|
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
|
|
class Handler(http.server.SimpleHTTPRequestHandler):
|
|
def end_headers(self):
|
|
# 预览页是刚由 go test 生成的,禁止缓存,否则改了 CSS 看不到变化
|
|
self.send_header("Cache-Control", "no-store, must-revalidate")
|
|
super().end_headers()
|
|
|
|
def log_message(self, fmt, *args):
|
|
# 只报 4xx/5xx,正常请求不刷屏
|
|
status = args[1] if len(args) > 1 else ""
|
|
if str(status).startswith(("4", "5")):
|
|
sys.stderr.write(" %s\n" % (fmt % args))
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description="TamaBox 主题生成器的本地预览服务")
|
|
ap.add_argument("--port", type=int, default=8777, help="端口,默认 8777")
|
|
ap.add_argument("--host", default="127.0.0.1", help="监听地址,默认 127.0.0.1")
|
|
args = ap.parse_args()
|
|
|
|
if not os.path.isdir(os.path.join(PROJECT_ROOT, "outputs")):
|
|
sys.stderr.write(
|
|
"还没生成预览快照。先在项目根执行:\n"
|
|
" TEST_PREVIEW=1 go test ./templates -run TestGenPreview\n"
|
|
)
|
|
|
|
handler = functools.partial(Handler, directory=PROJECT_ROOT)
|
|
socketserver.TCPServer.allow_reuse_address = True
|
|
with socketserver.TCPServer((args.host, args.port), handler) as httpd:
|
|
url = "http://%s:%d/dev/theme-builder/" % (args.host, args.port)
|
|
print("项目根:%s" % PROJECT_ROOT)
|
|
print("生成器:%s" % url)
|
|
print("Ctrl+C 停止")
|
|
try:
|
|
httpd.serve_forever()
|
|
except KeyboardInterrupt:
|
|
print("\n已停止")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|