Document and publish Studio tools through ordinary Wiki APIs
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
这篇 post 是两件 Studio 封面工具的使用入口与 API 教程。工具负责可编辑预览和渲染;waterfall-wiki 保存说明、图片、来源链接与调用示例。当前只做模板与主题占位,不填入具体 Skills 合集。
|
||||
|
||||
## 打开工具
|
||||
|
||||
- [同一母版/四张试版](/studio/ideas/learning-skills-template):一张空白母版,加笔记复习、教学资料、英语学习三张占位封面。四张共用 ref-17 九个构件的坐标;可统一改英文刊头,分别改中文标题、简介和配色。
|
||||
- [三种构图/模板选择工具](/studio/ideas/layout-selection):切换 ref-01、ref-07、ref-26,每种构图独立编辑文字、主图色、副图色。切换模板保留当前页内各模板的修改;刷新恢复初始值。
|
||||
|
||||
Studio 没有独立目录首页;访问 /studio 会回到 waterfall-wiki。请从本文的具体工具链接进入。页面上的下载可以保存 SVG、参数 JSON;工具不自动把试版发布为 Wiki post。
|
||||
|
||||
## 先看图,再选模板
|
||||
|
||||
{{GALLERY}}
|
||||
|
||||
## 构件对应与来源
|
||||
|
||||
| 工具 | 模板 | 构图 | 构件数 |
|
||||
| --- | --- | --- | --- |
|
||||
| 同一母版四张试版 | [ref-17](/explore/7b1b2fc5b9690213bbbe07ac) | 两行英文刊头、圆形叠标、左下主图、右侧窄栏 | 9 |
|
||||
| 三种构图 | [ref-01](/explore/eb937b42be92a9f6feee684f) | 双矩形错位、品牌字标、左下说明 | 6 |
|
||||
| 三种构图 | [ref-07](/explore/7d4cefdfada28bbe9e570af3) | 同心圆主图、环形文字、底部年份与说明 | 7 |
|
||||
| 三种构图 | {{REF26}} | 三行英文、右侧竖排标题、曲线与小图 | 11 |
|
||||
|
||||
完整来源:[27 张原图 Router](/explore/8eb8c76500580f7a4b56ba3c)。各原图 post 内有文本、图片、原画板与 T/I/D 坐标,阅读纯文本可加 ?agent_mode=True。构件必须带上完整键,例如 ref-01/T01;不同模板不能混用同名构件。
|
||||
|
||||
T 是可编辑文字,I 是图片位或色块,D 是装饰。ref-01 的品牌字标只有一个 T01,左下说明为 T02,没有重复 T03。工具保持每张原图的画板比例。ref-17 为重新绘制的文字、色块与线条;另外三种模板的图位保留原始形状蒙版,装饰沿用从原图分离的 PNG 图层,文字重新排版。它们是来源约束下的试版,替换内容后的字形与色调不会与原图逐像素相同。低分辨率原图的装饰放大仍可能有像素感。
|
||||
|
||||
## API:读取模板与原始试版
|
||||
|
||||
接口统一使用 waterfall-wiki 的同一域名。所有接口说明先读:
|
||||
|
||||
- [Studio API 目录](/studio/api)
|
||||
- [封面工具 API 纯文本说明](/studio/api/export/learning-skills-template?agent_mode=True)
|
||||
|
||||
```bash
|
||||
BASE='http://8.211.154.134:18131'
|
||||
# version:2,四张试版、共享模板、原始几何和来源链接
|
||||
curl "$BASE/studio/api/export/learning-skills-template"
|
||||
# 第 2 张初始封面,slide 从 0 开始
|
||||
curl "$BASE/studio/api/export/learning-skills-template?format=svg&slide=1" -o notes.svg
|
||||
# version:3,三种模板的 slots、fields、内嵌图位蒙版与装饰
|
||||
curl "$BASE/studio/api/export/layout-selection"
|
||||
# 指定原图模板
|
||||
curl "$BASE/studio/api/export/layout-selection?format=svg&layoutId=ref-07" -o circle.svg
|
||||
```
|
||||
|
||||
GET 省略 format 返回 JSON;format=markdown 返回文本资料;format=svg 返回初始封面。JSON 是 AI 可读取的几何与文字数据,SVG 是可独立打开的图像。
|
||||
|
||||
## API:提交自己的参数生成 SVG
|
||||
|
||||
POST 是无状态渲染,不修改 Wiki 或工具初始数据。Content-Type 为 application/json;format=svg 为默认值,format=json 返回经过校验的参数与模板。请求最大 32KB,返回的正式 SVG 不带构件辅助线。
|
||||
|
||||
```bash
|
||||
curl -X POST "$BASE/studio/api/export/learning-skills-template?format=svg" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{"slide":1,"heading":"笔记复习","body":"课程 · 笔记\n复习主题占位\n正文稍后填入","color":"#7a8b7e","accent":"#d5ddcf","masthead":["STUDY","Skills"]}' \
|
||||
-o notes-custom.svg
|
||||
|
||||
curl -X POST "$BASE/studio/api/export/layout-selection?format=svg" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{"layoutId":"ref-01","fields":{"T01":"STUDY","T02":"笔记复习\n主题说明占位\n正文稍后填入"},"color":"#83918a","accent":"#c3cec5"}' \
|
||||
-o ref-01-custom.svg
|
||||
```
|
||||
|
||||
四张试版参数:slide 为 0–3 的整数;heading 为 1–6 字;body 最多 360 字、3 行;masthead 为两行英文,每行 1–8 个英文字母或空格。多模板参数:layoutId 必填;fields 只能使用目标模板真实存在的 T 编号,每项 1–120 字、最多 4 行;未提供的字段保留默认值。颜色必须是 #RRGGBB。未知模板、构件 ID、参数、超长文字或无效颜色返回 400;不存在的工具返回 404;不支持的方法返回 405;请求过大返回 413。
|
||||
|
||||
## HTML / CSS 使用
|
||||
|
||||
只需展示初始封面时,直接把 SVG 接口作为图像,无需部署新页面或复制图层:
|
||||
|
||||
```html
|
||||
<figure class="cover">
|
||||
<img src="http://8.211.154.134:18131/studio/api/export/learning-skills-template?format=svg&slide=1" alt="笔记复习占位封面">
|
||||
</figure>
|
||||
<style>
|
||||
.cover { width: min(100%, 452px); margin: 0; }
|
||||
.cover img { display: block; width: 100%; height: auto; }
|
||||
</style>
|
||||
```
|
||||
|
||||
## React 使用
|
||||
|
||||
```tsx
|
||||
export function TemplateCover({ slide = 1 }: { slide?: number }) {
|
||||
return <img
|
||||
src={`http://8.211.154.134:18131/studio/api/export/learning-skills-template?format=svg&slide=${slide}`}
|
||||
alt="同一母版的合集封面"
|
||||
style={{ width: '100%', height: 'auto', display: 'block' }}
|
||||
/>;
|
||||
}
|
||||
```
|
||||
|
||||
自定义渲染:通过你的服务端调用 POST,保存返回的 SVG 或将其作为图像响应交给浏览器。接口没有开放任意跨站 JavaScript 的 CORS;跨域浏览器可以用 img 展示 GET 图片,POST 则从你自己的后端调用。
|
||||
|
||||
## Next.js 使用
|
||||
|
||||
```ts
|
||||
// app/api/cover/route.ts:为自己应用生成 SVG,不把远端 SVG 插入 innerHTML。
|
||||
export async function GET() {
|
||||
const upstream = await fetch(
|
||||
'http://8.211.154.134:18131/studio/api/export/layout-selection?format=svg',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
layoutId: 'ref-26',
|
||||
fields: { T01: 'Study 01', T02: 'Learn', T03: 'Create', T04: '学习合集' },
|
||||
color: '#7c8e9b'
|
||||
}),
|
||||
cache: 'no-store'
|
||||
}
|
||||
);
|
||||
if (!upstream.ok) return new Response('封面生成失败', { status: 502 });
|
||||
return new Response(await upstream.arrayBuffer(), {
|
||||
headers: { 'Content-Type': 'image/svg+xml; charset=utf-8' }
|
||||
});
|
||||
}
|
||||
// 页面中使用 <img src="/api/cover" alt="学习合集封面" />。
|
||||
```
|
||||
|
||||
如果自己的站点使用 HTTPS,应通过同源后端代理此 HTTP 服务,避免浏览器混合内容限制。
|
||||
|
||||
## 试版验收
|
||||
|
||||
1. 四张同母版封面的 ref-17 构件数、坐标、图层次序完全相同;只替换文字与颜色。
|
||||
2. 多模板工具的 ref-01、ref-07、ref-26 分别有 6、7、11 个构件,不能偷换构图。
|
||||
3. T 文本可编辑,I 图片位是占位色块,D 装饰不冒充文字;原图未辨识的微字不猜写为“原文”。
|
||||
4. 浏览器预览与 API 使用同一渲染组件。修改后不溢出;环形、竖排、两矩形错位保持原位置。
|
||||
5. SVG 独立打开无缺图;API 不需要先访问 Studio 首页;教程可作为普通 Wiki post 的纯文本读取。
|
||||
6. 本阶段只有模板和主题占位,没有自动生成或发布三篇具体 Skills 合集。
|
||||
@@ -0,0 +1,18 @@
|
||||
/** Publish a normal Wiki tutorial using only generic post, tag and media APIs. */
|
||||
import {readFile,writeFile,mkdir} from 'node:fs/promises';
|
||||
const base=(process.env.WIKI_BASE||'http://8.211.154.134:18131').replace(/\/$/,''),out='output/studio-tools',publicId='9ebd8f78d54b4b9db741b4e8';
|
||||
await mkdir(out,{recursive:true});
|
||||
async function api(path,options={}){const r=await fetch(base+path,{...options,signal:AbortSignal.timeout(60000)});if(r.status===404)return null;if(!r.ok)throw Error(path+': '+r.status+' '+(await r.text()).slice(0,300));return r.json()}
|
||||
const post=(path,body,method='POST')=>api(path,{method,headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});
|
||||
let state;try{state=JSON.parse(await readFile(out+'/published.json','utf8'))}catch{state={base,assets:{}}}if(state.base!==base)throw Error('Wrong publish target');
|
||||
for(const name of ['cover.png','ref17-master.png','ref17-notes.png','ref17-teaching.png','ref17-english.png','ref-01.png','ref-07.png','ref-26.png']){
|
||||
if(state.assets[name])continue;const form=new FormData();form.set('kind','image');form.set('assetOnly','true');form.append('files',new Blob([await readFile(out+'/'+name)],{type:'image/png'}),name);
|
||||
const data=await api('/api/upload',{method:'POST',body:form});state.assets[name]=data.assets[0];await writeFile(out+'/published.json',JSON.stringify(state,null,2));
|
||||
}
|
||||
const ids=JSON.parse(await readFile('content/layout-reference/post-ids.json','utf8'));
|
||||
const gallery=Object.entries(state.assets).filter(([name])=>name!=='cover.png').map(([name,id])=>``).join('\n\n');
|
||||
const text=(await readFile('content/studio-tools/tutorial.md','utf8')).replace('{{GALLERY}}',gallery).replace('{{REF26}}',`[ref-26](/explore/${ids['ref-26']})`);
|
||||
const tag=await post('/api/tags',{name:'Studio工具',slug:'studio-tools'}),title='Studio 封面工具 · 同一母版与三种构图',description='从 waterfall-wiki 进入两件 Studio 工具:ref-17 四张同母版试版,以及 ref-01/07/26 三种构图。含可访问图像、GET/POST API、HTML/React/Next.js 示例与构件验收。';
|
||||
const assets=Object.values(state.assets).map(id=>({id,role:'content'})),coverAssetId=state.assets['cover.png'],old=await api('/api/notes/'+publicId);
|
||||
state.post=old?await post('/api/notes/'+publicId,{title,description,markdown:text,tags:[tag.id],assets,coverAssetId,revision:old.entry.revision},'PUT'):await post('/api/posts',{publicId,slug:'studio-cover-tools',kind:'article',title,description,text,tags:[tag.id],assets,coverAssetId});
|
||||
await writeFile(out+'/published.json',JSON.stringify(state,null,2));console.log(base+'/explore/'+publicId);
|
||||
Reference in New Issue
Block a user