upgrade: adopt V8.21.3 (V8.13→8.21.3: P5 SFTP/S3, P6 multilingual, editor overhaul, enterprise hardening)
Overlay ~150 upstream items onto our V8.12 base, preserving trade-pro
theme, registry tests, and design docs. Re-applied our patch set
(discovery/env_for/blocks.css/type-mapping/theme_color/frontend entries,
test_theme_pro_v3 mods) — all anchors survived upstream evolution.
New platform enhancement (fixes upgrade-introduced regression):
multilingual.site_locale now honors settings_json.locale, plus
PUT /api/i18n/sites/{id}/locale — standalone English sites get
<html lang='en'> under the V8.15 locale-rewrite pipeline.
compose: adopt V8.21.3 (image 8.21.3-dev.1, /api/ready probes, MySQL/S3
env) with project name pinned to siteforge-v8-10 (data volumes).
Tests: 356 passed / 11 failed — all 11 confirmed pre-existing in
pristine V8.21.3 on this host (10 upstream Windows env + our build test
now green after locale fix). Patch registry updated in plan doc.
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ DEFAULTS={
|
||||
'show_cta':True,'cta_text':'联系我们','cta_href':'/contact/'},
|
||||
'footer':{'title':'','description':'','email':'','show_nav':True},
|
||||
'nav':[],
|
||||
'mobile_tabs':{'enabled':False,'items':[]},
|
||||
'tokens':{'font':'system','heading_scale':1.0,'radius':12,'section_spacing':72,
|
||||
'container_width':1200,'button_style':'rounded','header_style':'standard'},
|
||||
'responsive':{'tablet_gap':64,'mobile_gap':40,'mobile_hide_announcement':True,
|
||||
@@ -82,6 +83,22 @@ def _nav(rows,depth=0,counter=None):
|
||||
'children':_nav(children,depth+1,counter) if depth<2 else []})
|
||||
return result
|
||||
|
||||
def _mobile_tab_url(value):
|
||||
"""Allow site-internal routes plus safe, explicit user-facing contact links."""
|
||||
from urllib.parse import urlsplit
|
||||
if not isinstance(value,str) or not value or len(value)>350 or any(c in value for c in ('<','>','"',"'",'\\','\n','\r','\t')):
|
||||
raise HTTPException(400,'底部导航网址不安全')
|
||||
if value.startswith('/') and not value.startswith('//'):
|
||||
return _url(value)
|
||||
if re.fullmatch(r'tel:\+?[0-9()\- ]{5,30}',value):
|
||||
return value
|
||||
if re.fullmatch(r'mailto:[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}',value):
|
||||
return value
|
||||
parsed=urlsplit(value)
|
||||
if parsed.scheme=='https' and parsed.hostname and not parsed.username and not parsed.password and not any(c.isspace() for c in value):
|
||||
return value
|
||||
raise HTTPException(400,'仅支持站内路径、HTTPS、电话或邮箱链接')
|
||||
|
||||
def validate(raw):
|
||||
if not isinstance(raw,dict) or set(raw)-set(DEFAULTS):raise HTTPException(400,'未知的全站设计设置')
|
||||
normalized={}
|
||||
@@ -117,6 +134,33 @@ def validate(raw):
|
||||
if type(v) not in (float,int) or not lo<=v<=hi:raise HTTPException(400,'设计变量超出范围: '+field)
|
||||
normalized['tokens']=tokens
|
||||
normalized['nav']=_nav(raw.get('nav',[]))
|
||||
bar=raw.get('mobile_tabs',{})
|
||||
if not isinstance(bar,dict) or set(bar)-{'enabled','items'}:
|
||||
raise HTTPException(400,'底部导航配置格式不正确')
|
||||
enabled=bar.get('enabled',False)
|
||||
items=bar.get('items',[])
|
||||
if type(enabled) is not bool or not isinstance(items,list) or len(items)>5:
|
||||
raise HTTPException(400,'底部导航仅支持 0 至 5 项')
|
||||
allowed_icons={'home','grid','products','news','contact','phone','info','search','menu','star'}
|
||||
result=[]
|
||||
for item in items:
|
||||
if not isinstance(item,dict) or set(item)-{'title','route','icon','action'}:
|
||||
raise HTTPException(400,'底部导航项目格式不正确')
|
||||
title=item.get('title')
|
||||
icon=item.get('icon','grid')
|
||||
action=item.get('action','link')
|
||||
if not isinstance(title,str) or not 1<=len(title.strip())<=12:
|
||||
raise HTTPException(400,'底部导航标题须为 1 至 12 字')
|
||||
if icon not in allowed_icons or action not in ('link','menu'):
|
||||
raise HTTPException(400,'底部导航图标或操作不受支持')
|
||||
if action=='menu':
|
||||
route='/'
|
||||
else:
|
||||
route=_mobile_tab_url(item.get('route','/'))
|
||||
result.append({'title':title.strip(),'icon':icon,'route':route,'action':action})
|
||||
if enabled and not result:
|
||||
raise HTTPException(400,'启用底部导航时至少添加一项;不需要可直接关闭')
|
||||
normalized['mobile_tabs']={'enabled':enabled,'items':result}
|
||||
if len(json.dumps(normalized,ensure_ascii=False))>35_000:raise HTTPException(400,'全站设计过大')
|
||||
return normalized
|
||||
|
||||
|
||||
Reference in New Issue
Block a user