Render saved Studio results directly by ID without an editor
This commit is contained in:
@@ -14,9 +14,15 @@ const {entry:current,tags}=await api('/api/notes/'+canonical),{entry:old}=await
|
||||
// Snapshot only non-secret editorial state. Retain the first backup across repeat publication.
|
||||
for(const [id,entry] of [[canonical,current],[legacy,old]]){try{await readFile(dir+'/'+id+'-before.json')}catch{await writeFile(dir+'/'+id+'-before.json',JSON.stringify(entry),{mode:0o600})}}
|
||||
const catalogue=await api('/studio/api/layouts');
|
||||
let markdown=(await readFile(dir+'/layouts-api.md','utf8')).replace('{{LAYOUTS}}',catalogue.layouts.filter(l=>l.family==='fixed').map(l=>`- [${l.id} · ${l.name}](${l.url}) · [参数](${l.api})`).join('\n'));
|
||||
const legacyOriginal=JSON.parse(await readFile(dir+'/'+legacy+'-before.json','utf8')).editableMarkdown;
|
||||
markdown+='\n\n## 兼容工具原说明与代码示例\n\n以下保留原工具的说明与 HTML/React/Next.js 调用示例;它们是无状态兼容接口,持久化页面请使用上文 CRUD。\n\n'+legacyOriginal;
|
||||
let markdown=(await readFile(new URL('../content/studio-tools/layouts-api.md',import.meta.url),'utf8')).replace('{{LAYOUTS}}',catalogue.layouts.filter(l=>l.family==='fixed').map(l=>`- [${l.id} · ${l.name}](${l.url}) · [参数](${l.api})`).join('\n'));
|
||||
const appendixMarker='## 兼容工具原说明与代码示例';
|
||||
if(current.editableMarkdown.includes(appendixMarker)){
|
||||
markdown+='\n\n'+current.editableMarkdown.slice(current.editableMarkdown.indexOf(appendixMarker));
|
||||
}else{
|
||||
if(old.archived)throw Error('Original toolbox appendix is missing; refusing to replace it with the archived redirect');
|
||||
const legacyOriginal=JSON.parse(await readFile(dir+'/'+legacy+'-before.json','utf8')).editableMarkdown;
|
||||
markdown+='\n\n'+appendixMarker+'\n\n以下保留原工具的说明与 HTML/React/Next.js 调用示例;它们是无状态兼容接口,持久化页面请使用上文 CRUD。\n\n'+legacyOriginal;
|
||||
}
|
||||
const tagIds=entry=>[...new Set([...(entry.systemTags||[]).map(t=>t.id),...entry.categories.map(name=>tags.find(t=>t.name===name)?.id).filter(Boolean)])];
|
||||
const body=(entry,text,title=entry.title)=>({title,description:entry.description,markdown:text,tags:tagIds(entry),assets:entry.assets,coverAssetId:entry.coverAssetId,revision:entry.revision});
|
||||
await api('/api/notes/'+canonical,{...body(current,markdown,'Studio 排版 API · 标准图文与27套固定构图'),description:'coding agent 经 HTTP 选择标准图文或27套固定构图,上传压缩展示素材,创建、读取、更新、归档与恢复稳定 ID 原生页面;含兼容工具箱。'},'PUT');
|
||||
|
||||
@@ -19,6 +19,7 @@ if(process.argv.includes('--finalize')){
|
||||
const final={canonical:base+'/explore/'+canonical,documentEntries:category?.total??null,legacyCompatible:!documentFailure,testRecordsArchived:results.length};
|
||||
await writeFile('/tmp/studio-library/final-verification.json',JSON.stringify(final,null,2));console.log(JSON.stringify(final));if(documentFailure)throw documentFailure;
|
||||
}else{
|
||||
for(const path of ['/studio/template-layout','/studio/template-layout?id=invalid'])assert.equal((await fetch(base+path)).status,404);
|
||||
const catalog=await api('/studio/api/layouts');assert.equal(catalog.layouts.length,28);
|
||||
const note=(await api('/api/notes/8eb8c76500580f7a4b56ba3c')).entry;
|
||||
const asset=note.coverAssetId||note.assets.find(a=>a.kind==='image').id;
|
||||
@@ -29,10 +30,11 @@ for(const layoutId of ['standard-01','ref-17']){
|
||||
const p={texts:{T01:{value:'API 验收 · 原生页面',fontFamily:'serif',fontWeight:600}},images:{I01:{assetId:asset,fit:'cover',cropX:35,cropY:65,zoom:1.2}},added:[{id:'user-api-check',kind:'text',role:'扩展正文',box:[10,120,60,9],direction:'horizontal',text:'通过 HTTP 保存并恢复',reading:'custom'}],layers:{'user-api-check':{z:20,rotation:2}}};
|
||||
let d=(await api('/api/studio/designs',{schemaVersion:1,layoutId,name:'API 验收 '+layoutId,parameters:p},'POST',201)).design;
|
||||
const id=d.id,postId=d.postId;
|
||||
assert.equal(d.url,'/studio/template-layout?id='+id);assert.equal(d.renderUrl,d.url);
|
||||
results.push({layoutId,id,url:d.url,revision:d.revision,postId,parameters:p});
|
||||
await writeFile('/tmp/studio-library/verification.json',JSON.stringify({results,preview:{asset,bytes,type:'image/webp'},pages:0},null,2));
|
||||
assert.deepEqual((await api('/api/studio/designs/'+id)).design.parameters,d.parameters);
|
||||
const html=await fetch(base+d.url).then(r=>r.text());assert.ok(html.includes('API 验收'));assert.ok(html.includes('user-api-check'));assert.ok(html.includes('/api/preview?'));assert.ok(!html.includes('src="/api/media/'));
|
||||
const html=await fetch(base+d.url).then(r=>r.text());assert.ok(html.includes('API 验收'));assert.ok(html.includes('user-api-check'));assert.ok(html.includes('/api/preview?'));assert.ok(!html.includes('src="/api/media/'));assert.ok(!/page-tools|workbench-top|contenteditable|编辑页面/.test(html));
|
||||
d=(await api('/api/studio/designs/'+id,{name:'API 验收修订 '+layoutId,parameters:p,revision:d.revision},'PATCH')).design;
|
||||
assert.equal(d.postId,postId);assert.equal(d.revision,2);
|
||||
await api('/api/studio/designs/'+id,{name:'过期写入',parameters:{},revision:1},'PATCH',409);
|
||||
|
||||
@@ -26,7 +26,13 @@ const assert=require('node:assert/strict');
|
||||
await page.goto(base+'/studio',{waitUntil:'networkidle'});assert.equal(await page.locator('.library-item').count(),27);await page.screenshot({path:dir+'/catalog-desktop.png',fullPage:true});
|
||||
const verify=JSON.parse(await readFile('/tmp/studio-library/verification.json','utf8'));
|
||||
for(const d of verify.results){await page.goto(base+d.url,{waitUntil:'networkidle'});assert.equal(await page.locator('img').count(),1);const current=await page.locator('img').evaluate(img=>({src:img.currentSrc,complete:img.complete,width:img.naturalWidth}));assert.ok(current.complete&¤t.width>0);assert.equal(new URL(current.src).pathname,'/api/preview');await page.locator('article.native-layout').screenshot({path:dir+'/'+d.layoutId+'-saved.png',style:'.page-tools{visibility:hidden}'});
|
||||
await page.getByRole('button',{name:'编辑页面',exact:true}).click();assert.equal(await page.locator('.workbench-top').count(),1);await page.screenshot({path:dir+'/'+d.layoutId+'-editor.png'});
|
||||
assert.equal(new URL(page.url()).pathname,'/studio/template-layout');
|
||||
assert.equal(await page.locator('button,input,textarea,select,[contenteditable],.page-tools,.workbench-top').count(),0);
|
||||
await page.locator('.native-copy').first().dblclick();await page.locator('.native-copy').first().dispatchEvent('contextmenu');
|
||||
assert.equal(await page.locator('button,input,textarea,select,[contenteditable],.workbench-top').count(),0);
|
||||
const plain=await browser.newContext({viewport:{width:1100,height:1000},javaScriptEnabled:false});
|
||||
await plain.route('**/*',async route=>{const u=new URL(route.request().url());if(u.origin===origin)return route.continue({headers:{...route.request().headers(),Authorization:'Bearer '+token}});return route.abort()});
|
||||
const direct=await plain.newPage();await direct.goto(base+d.url,{waitUntil:'networkidle'});assert.equal(await direct.locator('article[data-design-id="'+d.id+'"]').count(),1);assert.ok((await direct.locator('article').innerText()).includes('API 验收'));await plain.close();
|
||||
}
|
||||
for(const slug of ['learning-skills-template','layout-selection']){await page.goto(base+'/studio/ideas/'+slug,{waitUntil:'networkidle'});await page.screenshot({path:dir+'/tool-'+slug+'.png',fullPage:true})}
|
||||
await page.setViewportSize({width:390,height:844});await page.goto(base+'/studio',{waitUntil:'networkidle'});assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));await page.screenshot({path:dir+'/catalog-mobile.png',fullPage:true});
|
||||
|
||||
Reference in New Issue
Block a user