chore: baseline V8.10.0-dev.1 with v8_workflows type-mapping fix and trade-pro spec
- 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>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
"""Validated per-device static CSS for pages and nested layout grids.
|
||||
|
||||
Uses exact IDs from validate_blocks, never user supplied CSS selectors/expressions.
|
||||
Desktop structure is preserved; tablet and mobile may have distinct spans/order.
|
||||
"""
|
||||
import re,json,hashlib
|
||||
from .blueprints import validate_blocks
|
||||
|
||||
_ID=re.compile(r'[a-zA-Z0-9_-]{4,50}\Z')
|
||||
|
||||
def editor_css(blocks,prefix=""):
|
||||
tablet=[];mobile=[]
|
||||
def walk(items):
|
||||
for b in items:
|
||||
uid=b['id']
|
||||
if not _ID.fullmatch(uid):continue
|
||||
s=b.get('style') or {}
|
||||
tp=s.get('tablet_padding',50);mp=s.get('mobile_padding',28)
|
||||
tp=max(0,min(120,int(tp)));mp=max(0,min(120,int(mp)))
|
||||
ident=prefix+f'.sf-section[data-block="{uid}"]'
|
||||
tablet.append(f'{ident}{{--sf-pad:{tp}px}}')
|
||||
mobile.append(f'{ident}{{--sf-pad:{mp}px}}')
|
||||
if s.get('hide_tablet') is True:tablet.append(f'{ident}{{display:none!important}}')
|
||||
if s.get('hide_mobile') is True:mobile.append(f'{ident}{{display:none!important}}')
|
||||
if b['type']=='layout':
|
||||
selector=prefix+f'.sf-layout-grid[data-layout="{uid}"]'
|
||||
tablet.append(f'{selector}{{grid-template-columns:repeat({b["tablet_columns"]},minmax(0,1fr))!important}}')
|
||||
mobile.append(f'{selector}{{grid-template-columns:repeat({b["mobile_columns"]},minmax(0,1fr))!important}}')
|
||||
for ci,col in enumerate(b['columns'],1):
|
||||
target=selector+f'>.sf-layout-column[data-col="{ci}"]'
|
||||
for dev,rules in [('tablet',tablet),('mobile',mobile)]:
|
||||
rules.append(f'{target}{{order:{col[dev+"_order"]};grid-column:span {col[dev+"_span"]}}}')
|
||||
if col['hide_'+dev]:rules.append(f'{target}{{display:none!important}}')
|
||||
walk(col['blocks'])
|
||||
walk(validate_blocks(blocks))
|
||||
# Breakpoints are intentionally exclusive: phone has tablet rules plus
|
||||
# phone overrides. Phone controls are appended after tablet controls.
|
||||
return '@media(max-width:820px){'+''.join(tablet)+'}@media(max-width:540px){'+''.join(mobile)+'}'
|
||||
|
||||
def route_key(route):
|
||||
return hashlib.sha256(str(route).encode()).hexdigest()[:16]
|
||||
|
||||
def site_css(entries):
|
||||
styles=[]
|
||||
for entry in entries:
|
||||
try:meta=json.loads(entry.get('meta_json') or '{}')
|
||||
except (TypeError,ValueError):continue
|
||||
if isinstance(meta.get('blocks'),list) and meta['blocks']:
|
||||
styles.append(editor_css(meta['blocks'],prefix=f'body[data-siteforge-route="{route_key(entry.get("route","/"))}"] '))
|
||||
# Identical generated IDs may occur on different pages. Styles are scoped
|
||||
# to block IDs and remain deterministic; validate each page independently.
|
||||
return ''.join(styles)
|
||||
Reference in New Issue
Block a user