upgrade: adopt V8.32.0-dev.1 (theme preview iframe fix, editor simple mode)

Overlay 46 upstream items; full patch set re-applied (all patched files
reset by overlay regardless of upstream change status — lesson recorded).
Verified: theme suites 35/35; dual-baseline 418 passed vs pristine
389+29 ours, FAILED-set diff EMPTY (same 10 Windows env fails).
Live: container 8.32.0-dev.1 healthy, hesupipe 192 entries intact,
trade-pro build 203 pages 0 errors.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-26 11:18:45 +08:00
parent 8884e100f9
commit 95982fad23
25 changed files with 561 additions and 232 deletions
+30 -8
View File
@@ -13,6 +13,7 @@ let libraryOpen=false;
let sharedPreview=null;
let siteNavDraft=[],siteDesignReviewToken=null;
let selectedChildId=null;
let advancedMode=false,themePreviewSeq=0,themePreviewAbort=null;
let inspectorTab='content',paletteSearch='',blockSearch='',previewRequest=0,previewController=null,savePromise=null,lastContinuousEdit=0,insertAfterId=null;
let snapshots=[],snapshotCursor=-1,draggedChild=null,draftRevision=0,siteDesign=null,siteDesignRevision=0,siteDesignPreview=null;
@@ -284,24 +285,40 @@ 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()},
closeThemePreview(){const dialog=$('#themePreviewModal');if(dialog?.open)dialog.close();const frame=$('#themePreviewFrame');if(frame)frame.removeAttribute('src')},
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;
$('#themePreviewFrameShell').style.width=width;
document.querySelectorAll('[data-theme-device]').forEach(btn=>btn.setAttribute('aria-pressed',String(btn.dataset.themeDevice===device)));
},
updateThemePreview(){
async updateThemePreview(){
const theme=$('#themePreviewTheme')?.value,page=$('#themePreviewPage')?.value;
const url=window.SiteForgeStudioExperience.previewUrl(theme,page);
if(!url)return;
const frame=$('#themePreviewFrame'),status=$('#themePreviewStatus');if(!frame)return;
status.textContent='正在加载主题示例…(不影响已保存的页面)';
frame.onload=()=>{if($('#themePreviewModal')?.open)status.textContent='已加载示例。可切换主题、页面类型和设备继续比较。'};
frame.onerror=()=>{status.textContent='示例加载失败,请稍后重试。'};
frame.src=url;
const frame=$('#themePreviewFrame'),status=$('#themePreviewStatus'),retry=$('#themePreviewRetry');if(!frame)return;
const seq=++themePreviewSeq;
themePreviewAbort?.abort();themePreviewAbort=new AbortController();
status.textContent='正在检查主题预览…';retry.hidden=true;frame.removeAttribute('src');frame.removeAttribute('srcdoc');
try{
const response=await fetch(url,{credentials:'same-origin',signal:themePreviewAbort.signal,cache:'no-store'});
if(!response.ok||!(response.headers.get('content-type')||'').includes('text/html'))throw Error(response.status===401?'登录已过期':`服务返回 ${response.status}`);
const content=await response.text();
if(seq!==themePreviewSeq||!$('#themePreviewModal')?.open)return;
// srcdoc keeps the preview in an opaque sandboxed origin: no allow-same-origin,
// no repeated HTML request, no frame-header conflict, and no access to editor data.
const base=new URL(url,['http:','https:'].includes(location.protocol)?location.href:'https://siteforge.invalid').href;
const safeBase=html(base);
const withBase=/<head(?:\s[^>]*)?>/i.test(content)?content.replace(/<head(\s[^>]*)?>/i,(match)=>match+`<base href=\"${safeBase}\">`):`<base href=\"${safeBase}\">`+content;
status.textContent='正在渲染主题页面…';
frame.onload=()=>{if(seq===themePreviewSeq&&$('#themePreviewModal')?.open)status.textContent='预览已显示 · 可切换主题和设备';};
frame.srcdoc=withBase;
}catch(error){
if(seq!==themePreviewSeq||error.name==='AbortError')return;
status.textContent='预览失败:'+error.message+'。请点击重新加载或在完整主题图库中打开。';retry.hidden=false;
}
}, toggleLibrary(force){libraryOpen=typeof force==='boolean'?force:!libraryOpen;const el=$('#sfLibraryPanel'),btn=$('#sfAddBlock');if(el)el.hidden=!libraryOpen;if(btn){btn.setAttribute('aria-expanded',String(libraryOpen));btn.textContent=libraryOpen?'× 收起区块库':'+ 添加内容区块'};Studio.updateGuidance()},
showHelp(){const dlg=$('#sfGuideDialog');if(dlg&&!dlg.open)dlg.showModal()},
updateGuidance(){const one=$('#sfStep1'),two=$('#sfStep2'),three=$('#sfStep3');if(!one||!two||!three)return;one.classList.toggle('active',selected<0||libraryOpen);two.classList.toggle('active',selected>=0&&!dirty&&!libraryOpen);three.classList.toggle('active',dirty);},
updateGuidance(){const one=$('#sfStep1'),two=$('#sfStep2'),three=$('#sfStep3');if(!one||!two||!three)return;one.classList.toggle('active',selected<0||libraryOpen);two.classList.toggle('active',selected>=0&&!dirty&&!libraryOpen);three.classList.toggle('active',dirty);const note=$('#sfNextAction');if(note)note.textContent=dirty?'更改尚未保存:点击右上角「保存草稿」':libraryOpen?'选择一种内容,添加后直接修改文字或图片':selected>=0?'正在编辑「'+(schema?.blocks?.[blocks[selected]?.type]?.name||'当前区块')+'」,右侧可直接修改':'先在左侧添加区块或选中页面内容';},
draw(){
const list=$('#blockList');$('#blockCount').textContent=blocks.length+' 区块';
const found=editorTools.filterBlocks(blocks,blockSearch,schema.blocks);
@@ -315,6 +332,8 @@ const Studio={
blockKeydown(event,i){if(event.target!==event.currentTarget)return;if(event.key==='Enter'||event.key===' '){event.preventDefault();Studio.select(i)}else if(event.altKey && (event.key==='ArrowUp'||event.key==='ArrowDown')){event.preventDefault();Studio.moveBlock(i,event.key==='ArrowUp'?-1:1)}},
changeInspectorTab(tab){if(!['content','style','responsive'].includes(tab))return;inspectorTab=tab;Studio.applyInspectorTab()},
applyInspectorTab(){for(const key of ['content','style','responsive']){const active=key===inspectorTab;const btn=$('#tab'+key.charAt(0).toUpperCase()+key.slice(1));btn?.setAttribute('aria-selected',String(active));const pane=$('[data-inspector-section="'+key+'"]');if(pane)pane.hidden=!active}},
toggleAdvanced(){advancedMode=!advancedMode;document.body.classList.toggle('sf-advanced-mode',advancedMode);const button=$('#studioAdvancedButton');button?.setAttribute('aria-pressed',String(advancedMode));if(button)button.textContent=advancedMode?'收起高级设置':'更多设计设置';const detail=$('#sfModeExplanation');if(detail)detail.textContent=advancedMode?'可调整布局、样式和手机显示':'当前显示常用设置';if(!advancedMode){inspectorTab='content';Studio.applyInspectorTab()}},
closePane(){document.body.classList.remove('show-inspector','show-structure')},
togglePane(which){const type=which==='left'?'show-structure':'show-inspector';document.body.classList.toggle(type);if(type==='show-structure')document.body.classList.remove('show-inspector');else document.body.classList.remove('show-structure');},
updateHistoryButtons(){const state=editorTools.historyState(snapshots,snapshotCursor);for(const id of ['#studioUndo','#quickUndo']){const el=$(id);if(el)el.disabled=!state.canUndo}for(const id of ['#studioRedo','#quickRedo']){const el=$(id);if(el)el.disabled=!state.canRedo}},
@@ -362,6 +381,9 @@ const Studio={
if(['title','subtitle','description','image','body'].includes(field))out+='<small class="prop-tip">可输入 {{site.name}}、{{page.title}} 等变量。</small>';
out+='</div>';
}
const mediaTarget=selectedChildId?editorTools.findChildInRoot(blocks,b.id,selectedChildId)?.child:b;
const mediaFields=schema.blocks[mediaTarget?.type]?.fields||[];
if(mediaTarget&&(['gallery','carousel'].includes(mediaTarget.type)||mediaFields.includes('image')))out+=`<div class="sf-photo-action"><strong>图片设置</strong><button class="ui-btn ghost" type="button" onclick="Studio.chooseMedia()">从图片素材库选择</button><label class="sf-direct-upload">或上传一张新图片<input aria-label="上传新图片" type="file" accept="image/png,image/jpeg,image/webp,image/gif,image/avif" onchange="Studio.uploadImage(this.files[0]);this.value=''"></label><small>预览先显示在草稿里,点击保存后才会保留。</small></div>`;
if(b.type==='layout')out+=Studio.childQuickEditor(b);
out+='</div><div data-inspector-section="responsive">';
if(b.type==='layout'){