upgrade: adopt V8.33.1 — final merge, our patches upstreamed natively
V8.33.0 absorbed our entire platform patch set as native features (discovery/env_for macro override/blocks.css priority/type-mapping fix/ frontend entries/locale override) and synced trade-pro from our remote; V8.33.1 adds HESU PIPE industrial-theme home fixes (1.3.1). Zero patch re-application this round — all verified native. Theme suites 35/35; full run 428 passed + same 10 known Windows env fails (FAILED set unchanged). Live: container 8.33.1-dev.1 healthy, hesupipe 192 entries intact, trade-pro build 203 pages 0 errors. Patch registry retired to archive — future upgrades are plain overlay. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
+7
-2
@@ -254,7 +254,9 @@ def render_blocks(blocks, site, page, entries):
|
||||
elif typ=='features':
|
||||
items=block.get('items') if isinstance(block.get('items'),list) else []
|
||||
cards=''.join(f'<article class="sf-feature"><span>{i+1:02}</span><h3>{escape(str(item.get("title","")))}</h3><p>{escape(str(item.get("text","")))}</p></article>' for i,item in enumerate(items[:12]) if isinstance(item,dict))
|
||||
parts.append(start+f'<div class="sf-section-head"><h2>{title}</h2></div><div class="sf-features">{cards}</div>'+end)
|
||||
visible_items=sum(1 for item in items[:12] if isinstance(item,dict))
|
||||
feature_cols=visible_items if 1<=visible_items<=4 else 3
|
||||
parts.append(start+f'<div class="sf-section-head"><h2>{title}</h2></div><div class="sf-features" style="--sf-feature-cols:{feature_cols}">{cards}</div>'+end)
|
||||
elif typ=='stats':
|
||||
items=block.get('items') if isinstance(block.get('items'),list) else []
|
||||
cards=''.join(f'<div><strong>{escape(str(item.get("value","")))}</strong><span>{escape(str(item.get("label","")))}</span></div>' for item in items[:12] if isinstance(item,dict))
|
||||
@@ -269,7 +271,10 @@ def render_blocks(blocks, site, page, entries):
|
||||
bound=[r for r in bound if brand_of(r)==str(page.get('title','')).casefold().strip()]
|
||||
count=max(1,min(48,int(block.get('limit') or 6)));cols=max(2,min(4,int(block.get('columns') or 3)))
|
||||
cards=''.join(f'<a class="sf-item" data-category="{escape(str((json.loads(r.get("meta_json") or "{}").get("fields") or {}).get("category", "")),quote=True)}" data-brand="{escape(str((json.loads(r.get("meta_json") or "{}").get("fields") or {}).get("brand", "")),quote=True)}" href="{safe_href(r.get("route") or "/")}"><div class="sf-item-img">'+(f'<img src="{safe_media(r.get("cover"))}" alt="{escape(r.get("title",""),quote=True)}" loading="lazy">' if safe_media(r.get('cover')) else '<div class="sf-placeholder">'+escape(r.get('title','')[:2])+'</div>')+f'</div><div class="sf-item-copy"><small>{escape(r.get("type",""))}</small><h3>{escape(r.get("title",""))}</h3><p>{escape((r.get("summary") or "")[:140])}</p><span>查看详情 →</span></div></a>' for r in bound[:count])
|
||||
parts.append(start+f'<div class="sf-section-head"><div><span class="sf-kicker">{subtitle}</span><h2>{title}</h2></div><a href="{safe_href(resolve("href","/product/"))}">{escape(resolve("button","查看全部"))} →</a></div><div class="sf-collection" style="--sf-cols:{cols}">{cards if cards else "<p class=\"sf-muted\">当前暂无相关内容,请导入或添加数据。</p>"}</div>'+end)
|
||||
# Homepage highlights are not catalogues: avoid injecting search/filters before featured cards.
|
||||
# Other listing pages may opt into filters for sufficiently large result sets.
|
||||
filterable=(page.get('route')!='/' and (page.get('type') in ('category','brand') or count>=9))
|
||||
parts.append(start+f'<div class="sf-section-head"><div><span class="sf-kicker">{subtitle}</span><h2>{title}</h2></div><a href="{safe_href(resolve("href","/product/"))}">{escape(resolve("button","查看全部"))}</a></div><div class="sf-collection" data-sf-filterable="{str(filterable).lower()}" style="--sf-cols:{cols}">{cards if cards else "<p class=\"sf-muted\">当前暂无相关内容,请导入或添加数据。</p>"}</div>'+end)
|
||||
elif typ=='image_text':
|
||||
im=safe_media(resolve('image')); img=f'<img src="{im}" alt="{title}" loading="lazy">' if im else '<div class="sf-placeholder sf-large">✧</div>'
|
||||
side=' sf-reverse' if block.get('image_side')=='left' else ''
|
||||
|
||||
+11
-1
@@ -269,6 +269,16 @@ def theme_preview_css(theme_id:str,version:str=''):
|
||||
except ValueError as exc:raise HTTPException(404,str(exc))
|
||||
return Response((dir/'theme.css').read_text(encoding='utf-8'),media_type='text/css')
|
||||
|
||||
@router.get('/themes/{theme_id}/preview.blocks.css')
|
||||
def theme_preview_blocks_css(theme_id:str,version:str=''):
|
||||
"""Serve the identical block stylesheet used by a static theme build."""
|
||||
from fastapi.responses import Response
|
||||
try:folder=theme_store.resolve(theme_id,version or None)
|
||||
except ValueError as exc:raise HTTPException(404,str(exc))
|
||||
chosen=folder/'blocks.css'
|
||||
if not chosen.is_file():chosen=theme_store.BASE.parent/'app'/'static'/'blocks.css'
|
||||
return Response(chosen.read_text(encoding='utf-8'),media_type='text/css',headers={'Cache-Control':'no-store'})
|
||||
|
||||
@router.get('/themes/{theme_id}/preview.pro.css')
|
||||
def theme_preview_pro_css(theme_id:str,version:str=''):
|
||||
from fastapi.responses import Response
|
||||
@@ -367,6 +377,6 @@ def theme_preview(theme_id:str,version:str='',page_type:str='home'):
|
||||
seo_description='SiteForge 主题模板预览')
|
||||
html=static_site.shell(html,site,page,records,[])
|
||||
html=html.replace('assets/theme.css',f'/api/factory/themes/{theme_id}/preview.css?version={version or theme_store.latest(theme_id)}')
|
||||
html=html.replace('assets/blocks.css','/ui/blocks.css').replace('assets/factory.css','/ui/factory.css')
|
||||
html=html.replace('assets/blocks.css',f'/api/factory/themes/{theme_id}/preview.blocks.css?version={version or theme_store.latest(theme_id)}').replace('assets/factory.css','/ui/factory.css')
|
||||
html=html.replace('assets/custom.css','/ui/factory.css').replace('assets/site-runtime.js','/ui/site-runtime.js')
|
||||
return HTMLResponse(html)
|
||||
|
||||
+1
-1
@@ -540,7 +540,7 @@ def editor_preview(site_id:str,entry_id:str,payload:BlueprintPayload):
|
||||
styles='\n'.join([
|
||||
(ROOT/'app'/'static'/'pro-base.css').read_text(encoding='utf-8'),
|
||||
(ROOT/'app'/'static'/'commercial.css').read_text(encoding='utf-8'),
|
||||
(ROOT/'app'/'static'/'blocks.css').read_text(encoding='utf-8'),
|
||||
((theme_dir/'blocks.css') if (theme_dir/'blocks.css').is_file() else (ROOT/'app'/'static'/'blocks.css')).read_text(encoding='utf-8'),
|
||||
(ROOT/'app'/'static'/'factory.css').read_text(encoding='utf-8'),
|
||||
(theme_dir/'pro.css').read_text(encoding='utf-8') if (theme_dir/'pro.css').exists() else '',
|
||||
(theme_dir/'theme.css').read_text(encoding='utf-8'),
|
||||
|
||||
+3
-2
@@ -97,7 +97,7 @@ def set_site_locale(site_id:str,data:LocaleIn,request:Request):
|
||||
enterprise.require(request,site_id,'owner')
|
||||
try:locale=locale_value(data.locale)
|
||||
except ValueError as exc:raise HTTPException(400,str(exc))
|
||||
site=db.one('SELECT settings_json FROM sites WHERE id=?',(site_id,))
|
||||
site=db.one('SELECT settings_json,theme FROM sites WHERE id=?',(site_id,))
|
||||
if not site:raise HTTPException(404,'网站不存在')
|
||||
settings=json.loads(site['settings_json'] or '{}')
|
||||
settings['locale']=locale
|
||||
@@ -195,12 +195,13 @@ def site_locale(site_id):
|
||||
row=db.one('SELECT locale FROM language_sites WHERE site_id=?',(site_id,))
|
||||
if row:return row['locale']
|
||||
# Per-site locale override (e.g. standalone English sites outside any language group)
|
||||
site=db.one('SELECT settings_json FROM sites WHERE id=?',(site_id,))
|
||||
site=db.one('SELECT settings_json,theme FROM sites WHERE id=?',(site_id,))
|
||||
try:override=json.loads(site['settings_json'] or '{}').get('locale') if site else None
|
||||
except ValueError:override=None
|
||||
if override:
|
||||
try:return locale_value(override)
|
||||
except ValueError:pass
|
||||
if site and site['theme']=='trade-pro':return 'en'
|
||||
return 'zh-CN'
|
||||
|
||||
def assert_build_safe(site_id):
|
||||
|
||||
@@ -27,3 +27,9 @@
|
||||
.sf-map-address{font-style:normal;font-size:18px;white-space:pre-line;line-height:1.8}
|
||||
@media (prefers-reduced-motion:reduce){.sf-slides{scroll-behavior:auto}.sf-item{transition:none}}
|
||||
@media(max-width:540px){.sf-slide{flex-basis:92%}}
|
||||
|
||||
/* V8.33.1 grid-class collision fix: block section and inner collection/features
|
||||
share a class. The section must NOT become a grid container itself.
|
||||
Restrict grid layout to its dedicated inner element. */
|
||||
.sf-section.sf-features,.sf-section.sf-collection,.sf-section.sf-stats,.sf-section.sf-gallery,.sf-section.sf-attributes{display:block;grid-template-columns:none}
|
||||
.sf-section.sf-faq{max-width:none}
|
||||
|
||||
@@ -284,7 +284,7 @@ const Studio={
|
||||
},
|
||||
filterPalette(value){paletteSearch=String(value||'').slice(0,70);Studio.renderPalette()},
|
||||
clearPaletteSearch(){paletteSearch='';const inp=$('#paletteSearch');if(inp){inp.value='';inp.focus()}Studio.renderPalette()},
|
||||
showThemePreview(){const dialog=$('#themePreviewModal');if(!dialog)return;const current=['automotive','industrial','corporate','minimal'].includes(site?.theme)?site.theme:'corporate';$('#themePreviewTheme').value=current;if(!dialog.open)dialog.showModal();Studio.themeDevice('desktop');Studio.updateThemePreview()},
|
||||
showThemePreview(){const dialog=$('#themePreviewModal');if(!dialog)return;const current=['automotive','industrial','corporate','minimal','trade-pro'].includes(site?.theme)?site.theme:'corporate';$('#themePreviewTheme').value=current;if(!dialog.open)dialog.showModal();Studio.themeDevice('desktop');Studio.updateThemePreview()},
|
||||
closeThemePreview(){themePreviewSeq++;themePreviewAbort?.abort();const dialog=$('#themePreviewModal');if(dialog?.open)dialog.close();const frame=$('#themePreviewFrame');if(frame){frame.removeAttribute('src');frame.removeAttribute('srcdoc')}},
|
||||
themeDevice(device){
|
||||
const width={desktop:'100%',tablet:'780px',phone:'390px'}[device];if(!width)return;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
const $=s=>document.querySelector(s), $$=s=>[...document.querySelectorAll(s)];
|
||||
const menu=$('.sf-menu-toggle');if(menu){menu.addEventListener('click',()=>{let open=menu.getAttribute('aria-expanded')!=='true';menu.setAttribute('aria-expanded',String(open));$('#sf-primary-links')?.classList.toggle('is-open',open);});}
|
||||
// Filter actual static content cards, no API is needed at runtime.
|
||||
$$('.sf-collection').forEach(grid=>{
|
||||
$$('.sf-collection[data-sf-filterable="true"]').forEach(grid=>{
|
||||
const cards=$$('.sf-item',grid);
|
||||
if(cards.length<2)return;
|
||||
const toolbar=document.createElement('div');toolbar.className='sf-filters';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* SiteForge V8.29: presentation-only studio helpers. No persisted-schema changes. */
|
||||
'use strict';
|
||||
(function(root){
|
||||
const themes=new Set(['automotive','industrial','corporate','minimal']);
|
||||
const themes=new Set(['automotive','industrial','corporate','minimal','trade-pro']);
|
||||
const pages=new Set(['home','listing','product','article','about','contact']);
|
||||
const categories={
|
||||
visual:new Set(['hero','banner','carousel','gallery','image','image_text','image-grid','video','slider']),
|
||||
@@ -29,7 +29,8 @@
|
||||
automotive:['#ffffff','#14283d','#f3f6f9','#102e47'],
|
||||
industrial:['#ffffff','#193f59','#eef5f8','#104a6d'],
|
||||
corporate:['#ffffff','#262d58','#f4f3fb','#34397b'],
|
||||
minimal:['#faf9f5','#292924','#f0efe9','#30302c']
|
||||
minimal:['#faf9f5','#292924','#f0efe9','#30302c'],
|
||||
'trade-pro':['#ffffff','#17365c','#eef4fc','#0b3d91']
|
||||
};
|
||||
function stylePreset(theme,preset){
|
||||
const [base,ink,soft,dark]=designPalettes[themes.has(theme)?theme:'corporate'];
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</div>
|
||||
<dialog id="themePreviewModal" class="sf-theme-showcase" aria-labelledby="themePreviewTitle">
|
||||
<div class="sf-showcase-head"><div><small>仅预览 · 不修改网站内容</small><h2 id="themePreviewTitle">选择适合网站的主题效果</h2><p>先比较布局和手机显示效果,再决定是否到「网站设计」中应用主题。</p></div><button type="button" class="ui-btn ghost" onclick="Studio.closeThemePreview()" aria-label="关闭主题预览">关闭 ×</button></div>
|
||||
<div class="sf-showcase-tools"><label>主题风格<select id="themePreviewTheme" onchange="Studio.updateThemePreview()"><option value="automotive">汽车与出行</option><option value="industrial">工业与制造</option><option value="corporate">企业品牌</option><option value="minimal">简约展示</option></select></label><label>参考页面<select id="themePreviewPage" onchange="Studio.updateThemePreview()"><option value="home">首页</option><option value="listing">产品列表</option><option value="product">产品详情</option><option value="article">文章详情</option><option value="about">关于我们</option><option value="contact">联系我们</option></select></label><div class="sf-showcase-device" role="group" aria-label="主题参考设备"><button type="button" data-theme-device="desktop" aria-pressed="true" onclick="Studio.themeDevice('desktop')">电脑</button><button type="button" data-theme-device="tablet" aria-pressed="false" onclick="Studio.themeDevice('tablet')">平板</button><button type="button" data-theme-device="phone" aria-pressed="false" onclick="Studio.themeDevice('phone')">手机</button></div></div>
|
||||
<div class="sf-showcase-tools"><label>主题风格<select id="themePreviewTheme" onchange="Studio.updateThemePreview()"><option value="automotive">汽车与出行</option><option value="industrial">工业与制造</option><option value="corporate">企业品牌</option><option value="minimal">简约展示</option><option value="trade-pro">Trade Pro · 外贸 B2B</option></select></label><label>参考页面<select id="themePreviewPage" onchange="Studio.updateThemePreview()"><option value="home">首页</option><option value="listing">产品列表</option><option value="product">产品详情</option><option value="article">文章详情</option><option value="about">关于我们</option><option value="contact">联系我们</option></select></label><div class="sf-showcase-device" role="group" aria-label="主题参考设备"><button type="button" data-theme-device="desktop" aria-pressed="true" onclick="Studio.themeDevice('desktop')">电脑</button><button type="button" data-theme-device="tablet" aria-pressed="false" onclick="Studio.themeDevice('tablet')">平板</button><button type="button" data-theme-device="phone" aria-pressed="false" onclick="Studio.themeDevice('phone')">手机</button></div></div>
|
||||
<p class="sf-showcase-status" id="themePreviewStatus" role="status" aria-live="polite">示例使用主题最新版本,可能与你网站锁定的主题版本不同。不会修改当前页面。</p><div class="sf-showcase-stage"><div id="themePreviewFrameShell" class="sf-showcase-frame"><iframe id="themePreviewFrame" title="主题示例页面" sandbox="allow-scripts" referrerpolicy="no-referrer" loading="lazy"></iframe></div></div>
|
||||
<div class="sf-showcase-footer"><span>编辑器仅查看外观;完整图库支持交互。预览不会保存或发布主题。<button type="button" id="themePreviewRetry" class="ui-btn ghost" onclick="Studio.updateThemePreview()" hidden>重新加载</button></span><a class="ui-btn ghost" href="/theme-gallery" target="_blank" rel="noopener">打开完整主题图库 ↗</a></div>
|
||||
</dialog>
|
||||
|
||||
@@ -45,6 +45,6 @@ theme.addEventListener('change',loadVersions);[page,version].forEach(control=>co
|
||||
retry.addEventListener('click',show);
|
||||
document.querySelectorAll('.devices button').forEach(button=>button.addEventListener('click',()=>{document.querySelectorAll('.devices button').forEach(other=>other.classList.remove('active'));button.classList.add('active');frame.style.width=button.dataset.size;}));
|
||||
const requestedTheme=new URLSearchParams(location.search).get('theme');
|
||||
if(['automotive','industrial','corporate','minimal'].includes(requestedTheme))theme.value=requestedTheme;
|
||||
if(['automotive','industrial','corporate','minimal','trade-pro'].includes(requestedTheme))theme.value=requestedTheme;
|
||||
loadVersions();
|
||||
</script></body></html>
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
"""Single source of truth for the SiteForge platform release identity."""
|
||||
import os
|
||||
VERSION = "8.32.0-dev.1"
|
||||
VERSION = "8.33.1-dev.1"
|
||||
PLATFORM_NAME = "SiteForge Factory"
|
||||
BUILD_SHA = os.environ.get("SITEFORGE_GIT_SHA", "unknown").strip()[:40] or "unknown"
|
||||
|
||||
Reference in New Issue
Block a user