68 lines
10 KiB
JavaScript
68 lines
10 KiB
JavaScript
|
|
/* Shared non-invasive shell for SiteForge specialized tools. No business API changes. */
|
|||
|
|
(()=>{'use strict';
|
|||
|
|
const PAGES={editor:{title:'可视化编辑器',group:'外观与设计',help:'编辑区块和全站布局,先保存草稿,确认后再发布设计。',more:'左侧调整页面结构,中间查看真实主题预览,右侧编辑属性。修改草稿不会改变已构建网站;发布设计后需要重新构建。',next:'完成设计后到「构建与交付」查看最终 HTML。'},theme:{title:'主题预览',group:'外观与设计',help:'比较四套主题及不同设备的页面效果。',more:'这里使用演示资料展示主题结构。选择真正的网站主题请返回工作台 → 外观与设计,切换后重新构建。',next:'返回外观与设计,选择主题并编辑页面。'},factory:{title:'高级建站',group:'专业工具',help:'主题版本、字段映射、SEO 审计及高级构建集中在这里。',more:'先选择网站,再按“总览 → 主题 → 内容 → 迁移检查 → 构建”逐步操作。普通内容管理请使用简洁工作台。',next:'数据准备完成后执行构建与质量检查。'},production:{title:'数据与迁移工具',group:'导入与迁移',help:'一次只处理一类数据:选工具 → 预览 → 审核 → 提交。',more:'WordPress、Excel/CSV、XML、API 与 MySQL 都进入可复核的暂存流程。高级参数可保留默认值,完成后回到工作台构建。',next:'在构建与交付中验证导入后的实际页面。'},operations:{title:'任务与交付',group:'构建与交付',help:'检查迁移问题、Worker 任务、构建质量和发布审批。',more:'按“迁移复核 → 构建任务 → 质量报告 → 审批 → 部署”顺序处理。部署操作会影响线上环境,请先核查版本和目标。',next:'通过质量与审批后,再选择目标环境发布。'}};
|
|||
|
|
const ctx=document.body?.dataset.sfPage; if(!PAGES[ctx])return; const p=PAGES[ctx],siteKey='siteforge:selected-site';
|
|||
|
|
const safeRead=()=>{try{return localStorage.getItem(siteKey)||''}catch{return ''}};
|
|||
|
|
const safeWrite=id=>{try{if(id)localStorage.setItem(siteKey,id);else localStorage.removeItem(siteKey)}catch{}};
|
|||
|
|
const route=(view)=>`/?view=${encodeURIComponent(view)}${selected()?`&site=${encodeURIComponent(selected())}`:''}`;
|
|||
|
|
const selected=()=>document.getElementById('sfGlobalSite')?.value || safeRead();
|
|||
|
|
const navbar=[['网站工作台','/','home'],['内容管理','content','content'],['外观设计','design','design'],['导入迁移','import','import'],['构建交付','build','build']];
|
|||
|
|
const html=document.createElement('div');html.className='sf-globalbar';html.setAttribute('role','navigation');html.setAttribute('aria-label','网站工作区导航');
|
|||
|
|
const main=document.createElement('div');main.className='sf-bar-main';
|
|||
|
|
const brand=document.createElement('a');brand.href='/';brand.className='sf-bar-brand';brand.innerHTML='<span class="sf-bar-logo">SF</span><span>SiteForge</span>';
|
|||
|
|
const loc=document.createElement('span');loc.className='sf-bar-loc';loc.textContent=`${p.group} / ${p.title}`;
|
|||
|
|
const nav=document.createElement('nav');nav.className='sf-bar-nav';nav.setAttribute('aria-label','主要功能');
|
|||
|
|
navbar.forEach(([label,view])=>{const a=document.createElement('a');a.textContent=label;a.dataset.sfView=view;a.href=view==='/'?'/':route(view);if((ctx==='editor'||ctx==='theme')&&view==='design'||ctx==='production'&&view==='import'||ctx==='operations'&&view==='build'){a.setAttribute('aria-current','page')}nav.append(a)});
|
|||
|
|
const sLabel=document.createElement('label');sLabel.className='sf-bar-site';sLabel.textContent='当前网站';const sel=document.createElement('select');sel.id='sfGlobalSite';sel.setAttribute('aria-label','当前管理的网站');sel.append(new Option('正在加载…',''));sLabel.append(sel);
|
|||
|
|
main.append(brand,loc,nav,sLabel);html.append(main);
|
|||
|
|
const help=document.createElement('div');help.className='sf-onboarding';help.dataset.open='false';
|
|||
|
|
const strong=document.createElement('strong');strong.textContent='当前任务';const short=document.createElement('span');short.textContent=p.help;const toggle=document.createElement('button');toggle.type='button';toggle.dataset.sfHelp='';toggle.textContent='查看操作说明 +';toggle.setAttribute('aria-expanded','false');
|
|||
|
|
const more=document.createElement('div');more.className='sf-onboarding-more';more.textContent=`操作方法:${p.more} 下一步:${p.next}`;help.append(strong,short,toggle,more);
|
|||
|
|
document.body.classList.add('sf-professional','sf-page-'+ctx);document.body.insertBefore(help,document.body.firstChild);document.body.insertBefore(html,help);
|
|||
|
|
const syncHeight=()=>{document.documentElement.style.setProperty('--sf-extras-height',(html.getBoundingClientRect().height+help.getBoundingClientRect().height)+'px')};
|
|||
|
|
const localSelect=()=>({factory:'#sitePicker',operations:'#site',production:'#site'})[ctx]||null;
|
|||
|
|
const refreshNav=()=>nav.querySelectorAll('a[data-sf-view]').forEach(a=>{a.href=a.dataset.sfView==='/'?'/':route(a.dataset.sfView)});
|
|||
|
|
const local=()=>document.querySelector(localSelect()||'#sf__no_local');
|
|||
|
|
let applying=false;
|
|||
|
|
function mirrorToLocal(id){const node=local();if(!node||!Array.from(node.options).some(o=>o.value===id))return false;if(node.value===id)return true;applying=true;node.value=id;node.dispatchEvent(new Event('change',{bubbles:true}));applying=false;return true}
|
|||
|
|
function selectSite(id){safeWrite(id);refreshNav();}
|
|||
|
|
window.SiteForgeShell={selectSite,selectedSite:selected};
|
|||
|
|
sel.addEventListener('change',()=>{const id=sel.value;if(!id)return;selectSite(id);if(ctx==='editor'){location.assign(route('design'));return}if(!mirrorToLocal(id)&&ctx!=='theme'){location.assign(route('overview'))}});
|
|||
|
|
nav.addEventListener('click',()=>selectSite(sel.value));
|
|||
|
|
toggle.addEventListener('click',()=>{const on=help.dataset.open!=='true';help.dataset.open=String(on);toggle.textContent=on?'收起操作说明 −':'查看操作说明 +';toggle.setAttribute('aria-expanded',String(on));syncHeight()});
|
|||
|
|
async function load(){try{const res=await fetch('/api/sites',{credentials:'same-origin'});if(!res.ok)throw Error('网站列表加载失败');const items=await res.json();sel.replaceChildren(new Option('选择网站',''));
|
|||
|
|
for(const item of items){sel.append(new Option(item.name,item.id))}
|
|||
|
|
const parts=location.pathname.split('/');const editorId=ctx==='editor'?parts[2]:'';const params=new URLSearchParams(location.search);const wanted=editorId||params.get('site')||safeRead()||items[0]?.id||'';
|
|||
|
|
if(items.some(s=>s.id===wanted)){sel.value=wanted;safeWrite(wanted)}else if(items.length){sel.value=items[0].id;safeWrite(sel.value)}
|
|||
|
|
const native=local();if(native){const apply=()=>{const desired=sel.value;if(Array.from(native.options).some(o=>o.value===desired))mirrorToLocal(desired)};if(native.options.length)apply();const observer=new MutationObserver(()=>{if(!applying)apply()});observer.observe(native,{childList:true});native.addEventListener('change',()=>{if(applying)return;sel.value=native.value;selectSite(native.value)})}
|
|||
|
|
refreshNav();}catch(err){sel.replaceChildren(new Option('在工作台选择网站',''));sel.disabled=true}syncHeight()}
|
|||
|
|
if(ctx==='operations'){
|
|||
|
|
// Business steps are optional views over existing DOM; hidden panels retain their API listeners.
|
|||
|
|
const content=document.querySelector('.layout > main');
|
|||
|
|
const groups=Array.from(content?.querySelectorAll(':scope > .two')||[]);
|
|||
|
|
const metrics=content?.querySelector('.metrics');
|
|||
|
|
if(groups.length===3&&metrics){
|
|||
|
|
const tabbar=document.createElement('div');tabbar.className='sf-ops-tabs';tabbar.setAttribute('role','tablist');tabbar.setAttribute('aria-label','运行与发布阶段');
|
|||
|
|
const choices=[['migration','① 导入与复核','确认旧站数据'],['build','② 生成静态网站','查看 Worker 任务'],['release','③ 审批与发布','核对部署目标']];
|
|||
|
|
const names=['migration','build','release'];groups.forEach((g,i)=>{g.dataset.sfOps=names[i];g.setAttribute('role','tabpanel')});
|
|||
|
|
function choose(key){groups.forEach(g=>g.hidden=g.dataset.sfOps!==key);tabbar.querySelectorAll('button').forEach(b=>b.setAttribute('aria-selected',String(b.dataset.view===key)));}
|
|||
|
|
choices.forEach(([key,title,desc])=>{const b=document.createElement('button');b.type='button';b.dataset.view=key;b.setAttribute('role','tab');const label=document.createElement('strong');label.textContent=title;const small=document.createElement('small');small.textContent=desc;b.append(label,small);b.addEventListener('click',()=>choose(key));tabbar.append(b)});
|
|||
|
|
metrics.insertAdjacentElement('afterend',tabbar);choose('migration');
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
// Mark required and invalid fields visibly, leaving native validation intact.
|
|||
|
|
document.addEventListener('invalid',ev=>{const field=ev.target;if(field.matches?.('input,select,textarea'))field.setAttribute('aria-invalid','true')},true);
|
|||
|
|
document.addEventListener('input',ev=>{if(ev.target.matches?.('input,select,textarea')&&ev.target.checkValidity())ev.target.removeAttribute('aria-invalid')});
|
|||
|
|
if(ctx==='editor'){
|
|||
|
|
const studio=document.querySelector('.studio');const top=studio?.querySelector('.studio-top');
|
|||
|
|
if(top){const reorder=(label,group,names)=>{const div=document.createElement('div');div.className='sf-editor-actiongroup';div.dataset.group=group;div.setAttribute('aria-label',label);for(const name of names){const node=Array.from(top.children).find(x=>x.tagName==='BUTTON' && (x.textContent||'').includes(name));if(node)div.append(node)}top.append(div)};
|
|||
|
|
reorder('页面与全站设计','design',['设计规范','全站页眉']);reorder('历史与草稿','draft',['撤销','重做','放弃草稿','保存草稿']);reorder('预览与构建','preview',['刷新预览','构建静态站']);reorder('发布设计','publish',['发布设计']);
|
|||
|
|
}
|
|||
|
|
const tabs=document.createElement('div');tabs.className='sf-editor-mobile-tabs';tabs.setAttribute('role','tablist');tabs.setAttribute('aria-label','手机端编辑面板');[['structure','页面结构'],['preview','页面预览'],['properties','属性设置']].forEach(([view,title])=>{const b=document.createElement('button');b.type='button';b.setAttribute('role','tab');b.dataset.sfPane=view;b.textContent=title;b.addEventListener('click',()=>setPane(view));tabs.append(b)});
|
|||
|
|
const body=studio?.querySelector('.studio-body');if(body)studio.insertBefore(tabs,body);
|
|||
|
|
function setPane(pane){document.body.dataset.mobilePane=pane;tabs.querySelectorAll('button').forEach(b=>b.setAttribute('aria-selected',String(b.dataset.sfPane===pane)))}setPane('structure');
|
|||
|
|
}
|
|||
|
|
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',load);else load();
|
|||
|
|
window.addEventListener('resize',syncHeight);
|
|||
|
|
})();
|