8cea7dee20
- SiteForge Enterprise V8.10.0-dev.1 upstream source (210 tests) - Fix: v8_workflows._mapped_input now reads both 类型/分类 columns so WXR imports keep post/product types instead of collapsing to page - Docs: trade-pro theme port spec (R3) and implementation plan Co-Authored-By: Claude Code <noreply@anthropic.com>
57 lines
4.6 KiB
Python
57 lines
4.6 KiB
Python
"""Trusted shared site shell for the four visual theme designs and installed themes.
|
|
Client-side search/filter works without a server after static export.
|
|
"""
|
|
from html import escape as e
|
|
import json
|
|
from pathlib import Path
|
|
from . import db
|
|
|
|
ASSETS=Path(__file__).resolve().parent/'static'
|
|
|
|
def shell(html,site,page,records,nav):
|
|
theme=site.get('theme','corporate')
|
|
label=lambda key: e(str(key or ''),quote=True)
|
|
links=[]
|
|
if not nav:
|
|
nav=[{'title':'首页','route':'/'},{'title':'产品中心','route':'/product/'},{'title':'品牌','route':'/brand/'},
|
|
{'title':'新闻资讯','route':'/post/'},{'title':'成功案例','route':'/case/'},{'title':'关于我们','route':'/about/'},{'title':'联系我们','route':'/contact/'}]
|
|
seen=set()
|
|
for n in nav[:25]:
|
|
route=n.get('route','/')
|
|
if route in seen:continue
|
|
seen.add(route)
|
|
links.append(f'<a href="{label(route.lstrip("/") or "./")}">{label(n.get("title"))}</a>')
|
|
# Themes can vary greatly in typography, but share an accessible responsive site shell.
|
|
name=label(site['name']); tag=label(site.get('tagline',''))
|
|
panels=''.join(f'<a href="{label(x["route"].lstrip("/") or "./")}"><span>{label(x["title"])}</span><small>{label(x["summary"][:86])}</small></a>' for x in records if x.get('type') in ('brand','service') and x.get('status')=='published')[:12000]
|
|
header=f'''<div class="sf-site-head" id="sf-site-head"><div class="sf-head-inner"><a class="sf-logo" href="./"><span class="sf-logo-mark">S</span><strong>{name}</strong></a>
|
|
<button type="button" class="sf-menu-toggle" aria-controls="sf-primary-links" aria-expanded="false" aria-label="展开网站菜单">☰</button>
|
|
<nav class="sf-primary-links" id="sf-primary-links" aria-label="主导航">{''.join(links)}<a href="search/" aria-label="站内搜索">⌕ 搜索</a></nav>
|
|
<a class="sf-head-cta" href="contact/">合作咨询 ↗</a></div>{('<div class="sf-mega">'+panels+'</div>') if panels else ''}</div>'''
|
|
foot=f'''<div class="sf-site-foot"><div class="sf-foot-grid"><div><h2>{name}</h2><p>{tag}</p></div><div><h4>快速导航</h4>{''.join(links[:6])}</div><div><h4>联系我们</h4><p>{label(site.get('email') or '请在管理后台填写联系邮箱')}</p><a href="contact/">提交咨询 →</a></div></div><div class="sf-foot-lower"><span>© {name}</span><span>GLOBAL SOURCING · GLOBAL DELIVERY</span></div></div>'''
|
|
# Every generated landing/detail page needs exactly one visible page heading.
|
|
# Blueprints for information pages start with h2 blocks by default.
|
|
if page.get('route') != '/' and '<main class="sf-canvas">' in html and '<h1' not in html:
|
|
intro=f'<section class="sf-page-intro"><div><p>EXPLORE / {label(page.get("type","PAGE")).upper()}</p><h1>{label(page.get("title", ""))}</h1><span>{label(page.get("summary", ""))}</span></div></section>'
|
|
html=html.replace('<main class="sf-canvas">',intro+'<main class="sf-canvas">',1)
|
|
html=html.replace('<body>','<body class="sf-theme-'+label(theme)+'">'+header,1)
|
|
html=html.replace('</body>',foot+'<script defer src="assets/site-runtime.js"></script></body>',1)
|
|
html=html.replace('</head>','<link rel="stylesheet" href="assets/factory.css"></head>',1)
|
|
return html
|
|
|
|
|
|
def write_assets(dist,records):
|
|
from shutil import copy2
|
|
copy2(ASSETS/'factory.css',dist/'assets'/'factory.css')
|
|
copy2(ASSETS/'site-runtime.js',dist/'assets'/'site-runtime.js')
|
|
search=[{'title':r['title'],'url':r['route'],'summary':(r.get('summary') or '')[:360],
|
|
'type':r['type'], 'brand':str((json.loads(r.get('meta_json') or '{}').get('fields') or {}).get('brand',''))}
|
|
for r in records if r.get('route') and r['route']!='/']
|
|
(dist/'search-index.json').write_text(json.dumps(search,ensure_ascii=False),encoding='utf-8')
|
|
|
|
|
|
def search_page(site):
|
|
from html import escape
|
|
return f'''<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><base href="/"><title>站内搜索 - {escape(site['name'])}</title><meta name="robots" content="noindex,follow"><link rel="stylesheet" href="assets/theme.css"><link rel="stylesheet" href="assets/factory.css"></head>
|
|
<body class="sf-theme-{escape(site['theme'])}"><main class="sf-search-page"><a href="./">← 返回首页</a><h1>搜索网站内容</h1><label for="sf-search">关键词</label><input id="sf-search" type="search" autofocus placeholder="搜索产品、新闻、品牌、案例……"><div id="sf-search-count" aria-live="polite"></div><div id="sf-search-results"></div></main><script defer src="assets/site-runtime.js"></script></body></html>'''
|