openclacky 1.3.10 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/lib/clacky/agent/skill_manager.rb +1 -0
- data/lib/clacky/brand_config.rb +32 -6
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +78 -2
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +390 -50
- data/lib/clacky/default_skills/media-gen/SKILL.md +182 -7
- data/lib/clacky/default_skills/media-gen/scripts/video_seq.sh +5 -3
- data/lib/clacky/extension/packager.rb +13 -3
- data/lib/clacky/extension/verifier.rb +1 -1
- data/lib/clacky/media/generator.rb +41 -0
- data/lib/clacky/media/volcengine.rb +394 -0
- data/lib/clacky/server/http_server.rb +107 -45
- data/lib/clacky/telemetry.rb +9 -0
- data/lib/clacky/tools/terminal.rb +24 -8
- data/lib/clacky/ui2/layout_manager.rb +4 -0
- data/lib/clacky/ui2/screen_buffer.rb +31 -3
- data/lib/clacky/utils/file_processor.rb +13 -8
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +189 -24
- data/lib/clacky/web/features/extensions/store.js +14 -7
- data/lib/clacky/web/features/extensions/view.js +77 -13
- data/lib/clacky/web/features/skills/view.js +61 -1
- data/lib/clacky/web/i18n.js +38 -6
- data/lib/clacky/web/index.html +6 -11
- data/lib/clacky/web/sessions.js +12 -3
- metadata +2 -1
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"published.confirm": "Unpublish {{id}} from the marketplace?",
|
|
50
50
|
"err.generic": "Something went wrong: {{msg}}",
|
|
51
51
|
"err.version_conflict": "Version {{ver}} has already been published. Please enter a higher version number above and try again.",
|
|
52
|
+
"err.invalid_version": "Invalid version. Use format like 1.0.0.",
|
|
52
53
|
"pub.version.label": "Version",
|
|
53
54
|
|
|
54
55
|
"publish.confirm.title": "Publish to the marketplace?",
|
|
@@ -100,12 +101,14 @@
|
|
|
100
101
|
"extlist.verify.ok": "Checks passed",
|
|
101
102
|
"extlist.verify.errors": "{{n}} error(s)",
|
|
102
103
|
"extlist.verify.warnings": "{{n}} warning(s)",
|
|
103
|
-
"extlist.btn.publish": "Publish",
|
|
104
|
+
"extlist.btn.publish": "Publish to Marketplace",
|
|
104
105
|
"extlist.btn.update": "Update",
|
|
105
|
-
"extlist.btn.pack": "Pack",
|
|
106
|
+
"extlist.btn.pack": "Pack (.zip)",
|
|
106
107
|
"extlist.btn.packing": "Packing…",
|
|
107
108
|
"extlist.btn.unpublish": "Unpublish",
|
|
108
109
|
"extlist.btn.iterate": "Iterate",
|
|
110
|
+
"extlist.btn.delete": "Delete",
|
|
111
|
+
"extlist.delete.confirm": "Delete local extension \"{{id}}\"? This cannot be undone.",
|
|
109
112
|
"extlist.iterate.seed": "Iterate on extension {{id}}",
|
|
110
113
|
"extlist.changelog.prompt": "Changelog (optional):",
|
|
111
114
|
"extlist.overwrite.confirm": "\"{{id}}\" is already published. Publish a new version?",
|
|
@@ -116,6 +119,44 @@
|
|
|
116
119
|
"extlist.newExt.hint": "Opens an AI session that scaffolds and builds it for you.",
|
|
117
120
|
"extlist.newExt.btn": "Create New Extension",
|
|
118
121
|
"extlist.newExt.seed": "I want to build a new OpenClacky extension. Help me get started: ask me what the extension should do, then scaffold it and build it out step by step.",
|
|
122
|
+
"unit.panel": "panel",
|
|
123
|
+
"unit.panels": "panels",
|
|
124
|
+
"unit.agent": "agent",
|
|
125
|
+
"unit.agents": "agents",
|
|
126
|
+
"unit.api": "API",
|
|
127
|
+
"unit.apis": "APIs",
|
|
128
|
+
"unit.skill": "skill",
|
|
129
|
+
"unit.skills": "skills",
|
|
130
|
+
|
|
131
|
+
"meta.section": "Extension Info",
|
|
132
|
+
"meta.edit.title": "Edit Extension Info",
|
|
133
|
+
"meta.edit": "Edit",
|
|
134
|
+
"meta.name.label": "Display name",
|
|
135
|
+
"meta.desc.label": "Description",
|
|
136
|
+
"meta.save": "Save",
|
|
137
|
+
"meta.saving": "Saving…",
|
|
138
|
+
"meta.saved": "Saved",
|
|
139
|
+
"meta.save.err": "Save failed: {{msg}}",
|
|
140
|
+
|
|
141
|
+
"entry.warn.title": "Access entry point not configured",
|
|
142
|
+
"entry.warn.body": "This extension has UI panels but no entry point is set. Users won't know how to open it after installing.",
|
|
143
|
+
"entry.warn.setup": "Set up entry point",
|
|
144
|
+
"entry.warn.skip": "Ignore and continue",
|
|
145
|
+
"entry.hint": "Choose where users can open this extension after installing it.",
|
|
146
|
+
"entry.none": "This extension has no UI panels — no entry points needed.",
|
|
147
|
+
"entry.unset": "Not configured — click Edit to set up.",
|
|
148
|
+
"entry.slot.label": "Slot",
|
|
149
|
+
"entry.kind.panel": "Panel",
|
|
150
|
+
"entry.add": "Add entry point",
|
|
151
|
+
"entry.remove": "Remove",
|
|
152
|
+
|
|
153
|
+
"slot.sidebar.nav": "Sidebar nav",
|
|
154
|
+
"slot.sidebar.nav.top": "Sidebar nav (top)",
|
|
155
|
+
"slot.sidebar.nav.bottom": "Sidebar nav (bottom)",
|
|
156
|
+
"slot.sidebar.footer": "Sidebar footer",
|
|
157
|
+
"slot.main.workspace": "Main workspace",
|
|
158
|
+
"slot.session.aside": "Session aside",
|
|
159
|
+
"slot.settings.tabs": "Settings tab",
|
|
119
160
|
"skills.section.cloud": "Cloud Skills",
|
|
120
161
|
"skills.section.cloudHint": "Published to the platform",
|
|
121
162
|
"skills.section.local": "Local Skills",
|
|
@@ -184,6 +225,7 @@
|
|
|
184
225
|
"published.confirm": "确定要从市场下架 {{id}} 吗?",
|
|
185
226
|
"err.generic": "出错了:{{msg}}",
|
|
186
227
|
"err.version_conflict": "版本 {{ver}} 已发布过,请在上方输入更高的版本号后重试。",
|
|
228
|
+
"err.invalid_version": "版本号格式不对,请使用如 1.0.0 的格式。",
|
|
187
229
|
"pub.version.label": "版本号",
|
|
188
230
|
|
|
189
231
|
"publish.confirm.title": "确定发布到市场?",
|
|
@@ -235,12 +277,14 @@
|
|
|
235
277
|
"extlist.verify.ok": "检查通过",
|
|
236
278
|
"extlist.verify.errors": "{{n}} 个错误",
|
|
237
279
|
"extlist.verify.warnings": "{{n}} 个警告",
|
|
238
|
-
"extlist.btn.publish": "
|
|
239
|
-
"extlist.btn.update": "
|
|
240
|
-
"extlist.btn.pack": "打包",
|
|
280
|
+
"extlist.btn.publish": "发布到市场",
|
|
281
|
+
"extlist.btn.update": "更新到市场",
|
|
282
|
+
"extlist.btn.pack": "打包(.zip)",
|
|
241
283
|
"extlist.btn.packing": "打包中…",
|
|
242
284
|
"extlist.btn.unpublish": "下架",
|
|
243
285
|
"extlist.btn.iterate": "迭代",
|
|
286
|
+
"extlist.btn.delete": "删除",
|
|
287
|
+
"extlist.delete.confirm": "确定要删除本地扩展「{{id}}」吗?此操作不可恢复。",
|
|
244
288
|
"extlist.iterate.seed": "迭代扩展 {{id}}",
|
|
245
289
|
"extlist.changelog.prompt": "更新说明(可选):",
|
|
246
290
|
"extlist.overwrite.confirm": "「{{id}}」已经发布过了。要发布新版本吗?",
|
|
@@ -251,6 +295,45 @@
|
|
|
251
295
|
"extlist.newExt.hint": "打开一个 AI 会话,帮你生成并开发它。",
|
|
252
296
|
"extlist.newExt.btn": "新建扩展",
|
|
253
297
|
"extlist.newExt.seed": "我想开发一个新的 OpenClacky 扩展。请引导我开始:先问清楚这个扩展要做什么,然后帮我搭好骨架并一步步开发出来。",
|
|
298
|
+
"unit.panel": "个面板",
|
|
299
|
+
"unit.panels": "个面板",
|
|
300
|
+
"unit.agent": "个 Agent",
|
|
301
|
+
"unit.agents": "个 Agent",
|
|
302
|
+
"unit.api": "个 API",
|
|
303
|
+
"unit.apis": "个 API",
|
|
304
|
+
"unit.skill": "个技能",
|
|
305
|
+
"unit.skills": "个技能",
|
|
306
|
+
|
|
307
|
+
"meta.section": "扩展信息",
|
|
308
|
+
"meta.edit.title": "编辑扩展信息",
|
|
309
|
+
"meta.edit": "编辑",
|
|
310
|
+
"meta.name.label": "显示名称",
|
|
311
|
+
"meta.desc.label": "描述",
|
|
312
|
+
"meta.save": "保存",
|
|
313
|
+
"meta.saving": "保存中…",
|
|
314
|
+
"meta.saved": "已保存",
|
|
315
|
+
"meta.save.err": "保存失败:{{msg}}",
|
|
316
|
+
|
|
317
|
+
"entry.section": "访问入口",
|
|
318
|
+
"entry.warn.title": "未配置访问入口",
|
|
319
|
+
"entry.warn.body": "该扩展包含 UI 面板,但尚未设置访问入口。安装后用户可能不知道如何打开它。",
|
|
320
|
+
"entry.warn.setup": "去设置",
|
|
321
|
+
"entry.warn.skip": "忽略,继续发布",
|
|
322
|
+
"entry.hint": "选择用户安装扩展后可以从哪里打开它。",
|
|
323
|
+
"entry.none": "该扩展没有 UI 面板,无需配置访问入口。",
|
|
324
|
+
"entry.unset": "尚未配置 — 点击编辑设置。",
|
|
325
|
+
"entry.slot.label": "挂载位置",
|
|
326
|
+
"entry.kind.panel": "面板",
|
|
327
|
+
"entry.add": "添加入口",
|
|
328
|
+
"entry.remove": "删除",
|
|
329
|
+
|
|
330
|
+
"slot.sidebar.nav": "侧边栏导航",
|
|
331
|
+
"slot.sidebar.nav.top": "侧边栏导航(顶部)",
|
|
332
|
+
"slot.sidebar.nav.bottom": "侧边栏导航(底部)",
|
|
333
|
+
"slot.sidebar.footer": "侧边栏底部",
|
|
334
|
+
"slot.main.workspace": "主工作区",
|
|
335
|
+
"slot.session.aside": "会话侧栏",
|
|
336
|
+
"slot.settings.tabs": "设置页标签",
|
|
254
337
|
"skills.section.cloud": "云端 Skills",
|
|
255
338
|
"skills.section.cloudHint": "已发布到平台",
|
|
256
339
|
"skills.section.local": "本地 Skills",
|
|
@@ -379,7 +462,6 @@
|
|
|
379
462
|
box.appendChild(bodyEl);
|
|
380
463
|
box.appendChild(footer);
|
|
381
464
|
overlay.appendChild(box);
|
|
382
|
-
overlay.addEventListener("click", (e) => { if (e.target === overlay) close(); });
|
|
383
465
|
document.body.appendChild(overlay);
|
|
384
466
|
return { overlay, bodyEl, footer, close };
|
|
385
467
|
}
|
|
@@ -473,14 +555,47 @@
|
|
|
473
555
|
}
|
|
474
556
|
}
|
|
475
557
|
|
|
558
|
+
function isSemver(v) {
|
|
559
|
+
return /^\d+\.\d+\.\d+$/.test(v);
|
|
560
|
+
}
|
|
561
|
+
|
|
476
562
|
// Unified publish flow: one modal that walks the creator from a release form
|
|
477
563
|
// (version + notes) through progress → device binding (if needed) → done.
|
|
478
564
|
// The creator owns the version; if it conflicts with the latest published
|
|
479
565
|
// version they can bump it inline — the modal writes it back to ext.yml
|
|
480
566
|
// before uploading. `prevVersionOrPromise` may be a value or a Promise.
|
|
481
567
|
// Resolves true when a publish succeeded, false otherwise.
|
|
482
|
-
function
|
|
568
|
+
function checkEntryPoints(ext) {
|
|
569
|
+
const hasPanels = (ext.units || []).some((u) => u.kind === "panel");
|
|
570
|
+
const hasEntries = Array.isArray(ext.entry_points) && ext.entry_points.length > 0;
|
|
571
|
+
if (!hasPanels || hasEntries) return Promise.resolve(true);
|
|
572
|
+
|
|
483
573
|
return new Promise((resolve) => {
|
|
574
|
+
const overlay = el("div", { class: "studio-modal-overlay" });
|
|
575
|
+
const modal = el("div", { class: "studio-modal", style: "max-width:400px" });
|
|
576
|
+
overlay.appendChild(modal);
|
|
577
|
+
document.body.appendChild(overlay);
|
|
578
|
+
|
|
579
|
+
function close(result) { document.body.removeChild(overlay); resolve(result); }
|
|
580
|
+
|
|
581
|
+
modal.appendChild(el("h3", { class: "studio-modal-title", text: t("entry.warn.title") }));
|
|
582
|
+
modal.appendChild(el("p", { style: "font-size:13px;color:var(--color-text-secondary);margin:0 0 20px", text: t("entry.warn.body") }));
|
|
583
|
+
|
|
584
|
+
const footer = el("div", { class: "studio-modal-footer" });
|
|
585
|
+
const skipBtn = el("button", { class: "studio-btn studio-btn-ghost", text: t("entry.warn.skip") });
|
|
586
|
+
skipBtn.addEventListener("click", () => close(true));
|
|
587
|
+
const setupBtn = el("button", { class: "studio-btn studio-btn-primary", text: t("entry.warn.setup") });
|
|
588
|
+
setupBtn.addEventListener("click", () => { close(false); openMetaEditModal(ext, () => store.reload()); });
|
|
589
|
+
footer.appendChild(skipBtn);
|
|
590
|
+
footer.appendChild(setupBtn);
|
|
591
|
+
modal.appendChild(footer);
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function runPublishFlow(ext, prevVersionOrPromise) {
|
|
596
|
+
return checkEntryPoints(ext).then((proceed) => {
|
|
597
|
+
if (!proceed) return false;
|
|
598
|
+
return new Promise((resolve) => {
|
|
484
599
|
let currentVersion = ext.version || "";
|
|
485
600
|
let isUpdate = false;
|
|
486
601
|
|
|
@@ -489,8 +604,15 @@
|
|
|
489
604
|
const verInput = el("input", { class: "studio-input", type: "text", value: currentVersion, placeholder: "1.0.0" });
|
|
490
605
|
verInput.addEventListener("input", () => {
|
|
491
606
|
currentVersion = verInput.value.trim();
|
|
492
|
-
|
|
493
|
-
|
|
607
|
+
const valid = isSemver(currentVersion);
|
|
608
|
+
verInput.classList.toggle("studio-input-error", !!currentVersion && !valid);
|
|
609
|
+
publishBtn.disabled = !valid;
|
|
610
|
+
publishBtn.textContent = valid ? t("pub.btn.publish", { ver: currentVersion }) : t("pub.btn.publish", { ver: currentVersion || "?" });
|
|
611
|
+
if (currentVersion && !valid) {
|
|
612
|
+
setProgress(t("err.invalid_version"), true);
|
|
613
|
+
} else {
|
|
614
|
+
status.style.display = "none";
|
|
615
|
+
}
|
|
494
616
|
});
|
|
495
617
|
verField.appendChild(verInput);
|
|
496
618
|
|
|
@@ -559,6 +681,13 @@
|
|
|
559
681
|
if (done || publishBtn.disabled) return;
|
|
560
682
|
const ver = currentVersion;
|
|
561
683
|
if (!ver) return;
|
|
684
|
+
if (!isSemver(ver)) {
|
|
685
|
+
setProgress(t("err.invalid_version", { ver }), true);
|
|
686
|
+
verInput.classList.add("studio-input-error");
|
|
687
|
+
verInput.focus();
|
|
688
|
+
verInput.select();
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
562
691
|
|
|
563
692
|
publishBtn.disabled = true;
|
|
564
693
|
publishBtn.textContent = t("pub.btn.publishing");
|
|
@@ -605,9 +734,8 @@
|
|
|
605
734
|
}
|
|
606
735
|
}
|
|
607
736
|
});
|
|
608
|
-
}
|
|
737
|
+
}); }
|
|
609
738
|
|
|
610
|
-
// ── Shared: extension list + current selection ─────────────────────────
|
|
611
739
|
// Both tabs care about "which local extension am I working on", so we keep a
|
|
612
740
|
// tiny page-level store and let each tab subscribe to changes.
|
|
613
741
|
const store = {
|
|
@@ -638,7 +766,7 @@
|
|
|
638
766
|
}
|
|
639
767
|
const select = el("select", { class: "studio-select" });
|
|
640
768
|
store.extensions.forEach((e) => {
|
|
641
|
-
const opt = el("option", { value: e.id, text: `${e.name} (${e.id})` });
|
|
769
|
+
const opt = el("option", { value: e.id, text: e.name ? `${e.name} (${e.id})` : e.id });
|
|
642
770
|
if (e.id === store.selectedId) opt.selected = true;
|
|
643
771
|
select.appendChild(opt);
|
|
644
772
|
});
|
|
@@ -710,29 +838,9 @@
|
|
|
710
838
|
}
|
|
711
839
|
|
|
712
840
|
function renderDetail() {
|
|
713
|
-
const detail = container.querySelector(".studio-detail");
|
|
714
|
-
if (!detail) return;
|
|
715
|
-
detail.innerHTML = "";
|
|
716
841
|
const ext = store.selected();
|
|
717
842
|
if (!ext) return;
|
|
718
|
-
|
|
719
|
-
detail.appendChild(el("h4", { class: "studio-detail-name", text: ext.name }));
|
|
720
|
-
if (ext.description) detail.appendChild(el("p", { class: "studio-detail-desc", text: ext.description }));
|
|
721
|
-
|
|
722
|
-
const meta = el("dl", { class: "studio-meta" });
|
|
723
|
-
const row = (label, value) => { meta.appendChild(el("dt", { text: label })); meta.appendChild(el("dd", { text: value || "—" })); };
|
|
724
|
-
row(t("detail.version"), ext.version);
|
|
725
|
-
row(t("detail.layer"), ext.layer);
|
|
726
|
-
detail.appendChild(meta);
|
|
727
|
-
|
|
728
|
-
detail.appendChild(el("div", { class: "studio-label", text: t("detail.units") }));
|
|
729
|
-
if (!ext.units.length) {
|
|
730
|
-
detail.appendChild(el("p", { class: "studio-empty", text: t("detail.noUnits") }));
|
|
731
|
-
} else {
|
|
732
|
-
const list = el("div", { class: "studio-units" });
|
|
733
|
-
ext.units.forEach((u) => list.appendChild(el("span", { class: "studio-unit-chip", text: `${u.kind} · ${u.id}` })));
|
|
734
|
-
detail.appendChild(list);
|
|
735
|
-
}
|
|
843
|
+
container.appendChild(buildExtInfoCard(ext, () => openMetaEditModal(ext, () => store.reload().then(rebuild))));
|
|
736
844
|
}
|
|
737
845
|
|
|
738
846
|
function rebuild() {
|
|
@@ -745,7 +853,7 @@
|
|
|
745
853
|
return;
|
|
746
854
|
}
|
|
747
855
|
|
|
748
|
-
|
|
856
|
+
renderDetail();
|
|
749
857
|
|
|
750
858
|
const bar = el("div", { class: "studio-actions" });
|
|
751
859
|
bar.appendChild(el("button", { class: "studio-btn studio-btn-primary studio-verify-status", text: t("btn.recheck"), onclick: runVerify }));
|
|
@@ -755,7 +863,6 @@
|
|
|
755
863
|
container.appendChild(el("div", { class: "studio-verify" }));
|
|
756
864
|
container.appendChild(el("p", { class: "studio-hint", text: t("hint.reload") }));
|
|
757
865
|
|
|
758
|
-
renderDetail();
|
|
759
866
|
runVerify();
|
|
760
867
|
}
|
|
761
868
|
|
|
@@ -773,6 +880,166 @@
|
|
|
773
880
|
}
|
|
774
881
|
|
|
775
882
|
// ── Publish tab ──────────────────────────────────────────────────────────
|
|
883
|
+
|
|
884
|
+
const PANEL_SLOTS = [
|
|
885
|
+
{ value: "sidebar.nav", labelKey: "slot.sidebar.nav" },
|
|
886
|
+
{ value: "sidebar.nav.top", labelKey: "slot.sidebar.nav.top" },
|
|
887
|
+
{ value: "sidebar.nav.bottom", labelKey: "slot.sidebar.nav.bottom" },
|
|
888
|
+
{ value: "sidebar.footer", labelKey: "slot.sidebar.footer" },
|
|
889
|
+
{ value: "main.workspace", labelKey: "slot.main.workspace" },
|
|
890
|
+
{ value: "session.aside", labelKey: "slot.session.aside" },
|
|
891
|
+
{ value: "settings.tabs", labelKey: "slot.settings.tabs" },
|
|
892
|
+
];
|
|
893
|
+
|
|
894
|
+
function buildExtInfoCard(ext, onEdit) {
|
|
895
|
+
const card = el("div", { class: "studio-detail" });
|
|
896
|
+
|
|
897
|
+
const head = el("div", { class: "studio-detail-head" });
|
|
898
|
+
head.appendChild(el("h4", { class: "studio-detail-name", text: ext.name || ext.id }));
|
|
899
|
+
const editBtn = el("button", { class: "studio-btn studio-btn-ghost studio-btn-sm", text: t("meta.edit") });
|
|
900
|
+
editBtn.addEventListener("click", onEdit);
|
|
901
|
+
head.appendChild(editBtn);
|
|
902
|
+
card.appendChild(head);
|
|
903
|
+
|
|
904
|
+
if (ext.description) card.appendChild(el("p", { class: "studio-detail-desc", text: ext.description }));
|
|
905
|
+
|
|
906
|
+
const infoRow = el("div", { class: "studio-info-row" });
|
|
907
|
+
infoRow.appendChild(el("span", { class: "studio-info-label", text: t("detail.version") }));
|
|
908
|
+
infoRow.appendChild(el("span", { class: "studio-info-value", text: ext.version || "—" }));
|
|
909
|
+
card.appendChild(infoRow);
|
|
910
|
+
|
|
911
|
+
const panelUnits = (ext.units || []).filter((u) => u.kind === "panel");
|
|
912
|
+
if (panelUnits.length) {
|
|
913
|
+
card.appendChild(el("div", { class: "studio-label studio-section-label", text: t("entry.section") }));
|
|
914
|
+
const eps = Array.isArray(ext.entry_points) && ext.entry_points.length ? ext.entry_points : null;
|
|
915
|
+
if (eps) {
|
|
916
|
+
const chips = el("div", { class: "studio-units" });
|
|
917
|
+
eps.forEach((ep) => {
|
|
918
|
+
const slotDef = PANEL_SLOTS.find((s) => s.value === ep.slot);
|
|
919
|
+
const label = slotDef ? t(slotDef.labelKey) : (ep.slot || ep.kind);
|
|
920
|
+
chips.appendChild(el("span", { class: "studio-unit-chip", text: label }));
|
|
921
|
+
});
|
|
922
|
+
card.appendChild(chips);
|
|
923
|
+
} else {
|
|
924
|
+
card.appendChild(el("p", { class: "studio-empty", text: t("entry.unset") }));
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
card.appendChild(el("div", { class: "studio-label studio-section-label", text: t("detail.units") }));
|
|
929
|
+
if (!(ext.units || []).length) {
|
|
930
|
+
card.appendChild(el("p", { class: "studio-empty", text: t("detail.noUnits") }));
|
|
931
|
+
} else {
|
|
932
|
+
const list = el("div", { class: "studio-units" });
|
|
933
|
+
ext.units.forEach((u) => list.appendChild(el("span", { class: "studio-unit-chip", text: u.kind })));
|
|
934
|
+
card.appendChild(list);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
return card;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function openMetaEditModal(ext, onSaved) {
|
|
941
|
+
let entries = Array.isArray(ext.entry_points) ? ext.entry_points.map((ep) => ({ ...ep })) : [];
|
|
942
|
+
const panelUnits = (ext.units || []).filter((u) => u.kind === "panel");
|
|
943
|
+
|
|
944
|
+
const overlay = el("div", { class: "studio-modal-overlay" });
|
|
945
|
+
overlay.addEventListener("click", (e) => { if (e.target === overlay) close(); });
|
|
946
|
+
|
|
947
|
+
const modal = el("div", { class: "studio-modal" });
|
|
948
|
+
overlay.appendChild(modal);
|
|
949
|
+
document.body.appendChild(overlay);
|
|
950
|
+
|
|
951
|
+
function close() { document.body.removeChild(overlay); }
|
|
952
|
+
|
|
953
|
+
modal.appendChild(el("h3", { class: "studio-modal-title", text: t("meta.edit.title") }));
|
|
954
|
+
|
|
955
|
+
const nameField = el("div", { class: "studio-field" });
|
|
956
|
+
nameField.appendChild(el("label", { class: "studio-label", text: t("meta.name.label") }));
|
|
957
|
+
const nameInput = el("input", { class: "studio-input", value: ext.name || "" });
|
|
958
|
+
nameField.appendChild(nameInput);
|
|
959
|
+
modal.appendChild(nameField);
|
|
960
|
+
|
|
961
|
+
const descField = el("div", { class: "studio-field" });
|
|
962
|
+
descField.appendChild(el("label", { class: "studio-label", text: t("meta.desc.label") }));
|
|
963
|
+
const descInput = el("textarea", { class: "studio-textarea", rows: "5", text: ext.description || "" });
|
|
964
|
+
descField.appendChild(descInput);
|
|
965
|
+
modal.appendChild(descField);
|
|
966
|
+
|
|
967
|
+
if (panelUnits.length) {
|
|
968
|
+
const entryField = el("div", { class: "studio-field" });
|
|
969
|
+
entryField.appendChild(el("label", { class: "studio-label", text: t("entry.section") }));
|
|
970
|
+
entryField.appendChild(el("p", { class: "studio-hint", style: "margin:0 0 8px", text: t("entry.hint") }));
|
|
971
|
+
|
|
972
|
+
const listEl = el("div", { class: "studio-entry-list" });
|
|
973
|
+
|
|
974
|
+
function renderEntryList() {
|
|
975
|
+
listEl.innerHTML = "";
|
|
976
|
+
entries.forEach((ep, idx) => {
|
|
977
|
+
const row = el("div", { class: "studio-entry-row" });
|
|
978
|
+
|
|
979
|
+
const slotSel = el("select", { class: "studio-select" });
|
|
980
|
+
PANEL_SLOTS.forEach(({ value, labelKey }) => {
|
|
981
|
+
const opt = el("option", { value, text: t(labelKey) });
|
|
982
|
+
if (ep.slot === value) opt.selected = true;
|
|
983
|
+
slotSel.appendChild(opt);
|
|
984
|
+
});
|
|
985
|
+
if (!ep.slot) entries[idx].slot = PANEL_SLOTS[0].value;
|
|
986
|
+
slotSel.addEventListener("change", () => { entries[idx].slot = slotSel.value; });
|
|
987
|
+
|
|
988
|
+
const removeBtn = el("button", { class: "studio-btn studio-btn-ghost studio-btn-sm", text: "✕" });
|
|
989
|
+
removeBtn.title = t("entry.remove");
|
|
990
|
+
removeBtn.addEventListener("click", () => { entries.splice(idx, 1); renderEntryList(); });
|
|
991
|
+
|
|
992
|
+
row.appendChild(slotSel);
|
|
993
|
+
row.appendChild(removeBtn);
|
|
994
|
+
listEl.appendChild(row);
|
|
995
|
+
});
|
|
996
|
+
|
|
997
|
+
const addBtn = el("button", { class: "studio-btn studio-btn-ghost studio-btn-sm", text: `+ ${t("entry.add")}` });
|
|
998
|
+
addBtn.addEventListener("click", () => {
|
|
999
|
+
const du = panelUnits[0];
|
|
1000
|
+
entries.push({ panel_id: du ? du.id : "", slot: PANEL_SLOTS[0].value });
|
|
1001
|
+
renderEntryList();
|
|
1002
|
+
});
|
|
1003
|
+
listEl.appendChild(addBtn);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
renderEntryList();
|
|
1007
|
+
entryField.appendChild(listEl);
|
|
1008
|
+
modal.appendChild(entryField);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const statusEl = el("span", { class: "studio-form-status" });
|
|
1012
|
+
const footer = el("div", { class: "studio-modal-footer" });
|
|
1013
|
+
const cancelBtn = el("button", { class: "studio-btn studio-btn-ghost", text: t("pub.btn.cancel"), onclick: close });
|
|
1014
|
+
const saveBtn = el("button", { class: "studio-btn studio-btn-primary", text: t("meta.save") });
|
|
1015
|
+
|
|
1016
|
+
saveBtn.addEventListener("click", async () => {
|
|
1017
|
+
saveBtn.disabled = true;
|
|
1018
|
+
saveBtn.textContent = t("meta.saving");
|
|
1019
|
+
statusEl.textContent = "";
|
|
1020
|
+
try {
|
|
1021
|
+
const payload = entries.map((ep) => ({ unit_id: ep.panel_id, slot: ep.slot }));
|
|
1022
|
+
await postJson("/set_meta", {
|
|
1023
|
+
ext_id: ext.id,
|
|
1024
|
+
name: nameInput.value.trim(),
|
|
1025
|
+
description: descInput.value.trim(),
|
|
1026
|
+
entry_points: payload,
|
|
1027
|
+
});
|
|
1028
|
+
close();
|
|
1029
|
+
if (onSaved) onSaved();
|
|
1030
|
+
} catch (e) {
|
|
1031
|
+
saveBtn.disabled = false;
|
|
1032
|
+
saveBtn.textContent = t("meta.save");
|
|
1033
|
+
statusEl.textContent = t("meta.save.err", { msg: e.message });
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
footer.appendChild(statusEl);
|
|
1038
|
+
footer.appendChild(cancelBtn);
|
|
1039
|
+
footer.appendChild(saveBtn);
|
|
1040
|
+
modal.appendChild(footer);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
776
1043
|
function createPublishPanel() {
|
|
777
1044
|
let container = null;
|
|
778
1045
|
let unsub = null;
|
|
@@ -810,13 +1077,27 @@
|
|
|
810
1077
|
}
|
|
811
1078
|
}
|
|
812
1079
|
|
|
1080
|
+
function renderPublishedSkeleton(box) {
|
|
1081
|
+
const card = el("div", { class: "studio-skill-card studio-skeleton-card" });
|
|
1082
|
+
const head = el("div", { class: "studio-skill-head" });
|
|
1083
|
+
head.appendChild(el("span", { class: "studio-skeleton studio-skeleton-name" }));
|
|
1084
|
+
head.appendChild(el("span", { class: "studio-skeleton studio-skeleton-badge" }));
|
|
1085
|
+
head.appendChild(el("span", { class: "studio-skeleton studio-skeleton-ver" }));
|
|
1086
|
+
head.appendChild(el("span", { class: "studio-skeleton studio-skeleton-btn" }));
|
|
1087
|
+
card.appendChild(head);
|
|
1088
|
+
box.appendChild(card);
|
|
1089
|
+
}
|
|
1090
|
+
|
|
813
1091
|
async function loadPublished() {
|
|
814
1092
|
const box = container.querySelector(".studio-published");
|
|
815
1093
|
if (!box) return;
|
|
816
1094
|
box.innerHTML = "";
|
|
817
1095
|
box.appendChild(el("div", { class: "studio-label", text: t("published.title") }));
|
|
1096
|
+
renderPublishedSkeleton(box);
|
|
818
1097
|
try {
|
|
819
1098
|
const data = await getJson("/published");
|
|
1099
|
+
box.innerHTML = "";
|
|
1100
|
+
box.appendChild(el("div", { class: "studio-label", text: t("published.title") }));
|
|
820
1101
|
const exts = data.extensions || [];
|
|
821
1102
|
if (!exts.length) { box.appendChild(el("p", { class: "studio-empty", text: t("published.empty") })); return; }
|
|
822
1103
|
exts.forEach((e) => {
|
|
@@ -826,7 +1107,7 @@
|
|
|
826
1107
|
const statusKind = e.status === "draft" ? "local" : "published";
|
|
827
1108
|
head.appendChild(el("span", { class: `studio-skill-badge studio-skill-badge-${statusKind}`, text: e.status === "draft" ? t("extlist.badge.draft") : t("extlist.badge.published") }));
|
|
828
1109
|
if (e.version) head.appendChild(el("span", { style: "font-size:11px;color:var(--color-text-muted);", text: "v" + e.version }));
|
|
829
|
-
const unpubBtn = el("button", { class: "studio-btn studio-btn-danger", text: t("btn.unpublish") });
|
|
1110
|
+
const unpubBtn = el("button", { class: "studio-btn studio-btn-danger studio-btn-sm", text: t("btn.unpublish") });
|
|
830
1111
|
unpubBtn.style.marginLeft = "auto";
|
|
831
1112
|
unpubBtn.addEventListener("click", async () => {
|
|
832
1113
|
if (!window.confirm(t("published.confirm", { id: e.id }))) return;
|
|
@@ -839,6 +1120,8 @@
|
|
|
839
1120
|
box.appendChild(card);
|
|
840
1121
|
});
|
|
841
1122
|
} catch (e) {
|
|
1123
|
+
box.innerHTML = "";
|
|
1124
|
+
box.appendChild(el("div", { class: "studio-label", text: t("published.title") }));
|
|
842
1125
|
box.appendChild(el("p", { class: "studio-empty", text: t("err.generic", { msg: e.message }) }));
|
|
843
1126
|
}
|
|
844
1127
|
}
|
|
@@ -848,7 +1131,10 @@
|
|
|
848
1131
|
container.innerHTML = "";
|
|
849
1132
|
container.appendChild(renderPicker(rebuild));
|
|
850
1133
|
|
|
851
|
-
|
|
1134
|
+
const ext = store.selected();
|
|
1135
|
+
if (!ext) return;
|
|
1136
|
+
|
|
1137
|
+
container.appendChild(buildExtInfoCard(ext, () => openMetaEditModal(ext, () => store.reload().then(rebuild))));
|
|
852
1138
|
|
|
853
1139
|
const bar = el("div", { class: "studio-actions" });
|
|
854
1140
|
bar.appendChild(el("button", { class: "studio-btn studio-btn-primary studio-publish-btn", text: t("btn.publish"), onclick: doPublish }));
|
|
@@ -924,6 +1210,15 @@
|
|
|
924
1210
|
return el("span", { class: "studio-skill-badge studio-skill-badge-" + kind, text });
|
|
925
1211
|
}
|
|
926
1212
|
|
|
1213
|
+
function formatUnitCounts(counts) {
|
|
1214
|
+
if (!counts || typeof counts !== "object") return "";
|
|
1215
|
+
return Object.keys(counts).map((kind) => {
|
|
1216
|
+
const n = parseInt(counts[kind], 10);
|
|
1217
|
+
if (!n) return null;
|
|
1218
|
+
return `${n} ${t("unit." + kind + (n > 1 ? "s" : ""))}`;
|
|
1219
|
+
}).filter(Boolean).join(" · ");
|
|
1220
|
+
}
|
|
1221
|
+
|
|
927
1222
|
function cloudCard(ext) {
|
|
928
1223
|
const card = el("div", { class: "studio-skill-card" });
|
|
929
1224
|
const head = el("div", { class: "studio-skill-head" });
|
|
@@ -938,7 +1233,7 @@
|
|
|
938
1233
|
|
|
939
1234
|
const actions = el("div", { class: "studio-actions" });
|
|
940
1235
|
actions.appendChild(el("button", { class: "studio-btn studio-btn-primary", text: t("extlist.btn.iterate"), onclick: () => createExtension(t("extlist.iterate.seed", { id: ext.id })) }));
|
|
941
|
-
const un = el("button", { class: "studio-btn studio-btn-ghost", text: t("extlist.btn.unpublish") });
|
|
1236
|
+
const un = el("button", { class: "studio-btn studio-btn-ghost studio-btn-sm", text: t("extlist.btn.unpublish") });
|
|
942
1237
|
un.addEventListener("click", async () => {
|
|
943
1238
|
if (!window.confirm(t("extlist.unpublish.confirm", { id: ext.id }))) return;
|
|
944
1239
|
un.disabled = true;
|
|
@@ -965,13 +1260,14 @@
|
|
|
965
1260
|
|
|
966
1261
|
const meta = el("div", { class: "studio-skill-meta" });
|
|
967
1262
|
if (ext.version) meta.appendChild(el("span", { text: "v" + ext.version }));
|
|
968
|
-
|
|
1263
|
+
const unitsText = formatUnitCounts(ext.unit_counts);
|
|
1264
|
+
if (unitsText) meta.appendChild(el("span", { text: unitsText }));
|
|
969
1265
|
card.appendChild(meta);
|
|
970
1266
|
|
|
971
1267
|
const actions = el("div", { class: "studio-actions" });
|
|
972
1268
|
const pub = el("button", { class: "studio-btn studio-btn-primary", text: published ? t("extlist.btn.update") : t("extlist.btn.publish") });
|
|
973
|
-
pub.disabled =
|
|
974
|
-
pub.title =
|
|
1269
|
+
pub.disabled = !!ext.error_count;
|
|
1270
|
+
pub.title = ext.error_count ? t("extlist.verify.errors", { n: ext.error_count }) : "";
|
|
975
1271
|
pub.addEventListener("click", () => doPublish(ext, cloudEntry ? cloudEntry.version : null));
|
|
976
1272
|
actions.appendChild(pub);
|
|
977
1273
|
|
|
@@ -980,6 +1276,12 @@
|
|
|
980
1276
|
packBtn.addEventListener("click", () => doPack(ext, packBtn));
|
|
981
1277
|
actions.appendChild(packBtn);
|
|
982
1278
|
card.appendChild(actions);
|
|
1279
|
+
|
|
1280
|
+
if (!published) {
|
|
1281
|
+
const delBtn = el("button", { class: "studio-btn studio-btn-danger", text: t("extlist.btn.delete") });
|
|
1282
|
+
delBtn.addEventListener("click", () => doDelete(ext, delBtn));
|
|
1283
|
+
actions.appendChild(delBtn);
|
|
1284
|
+
}
|
|
983
1285
|
return card;
|
|
984
1286
|
}
|
|
985
1287
|
|
|
@@ -995,6 +1297,25 @@
|
|
|
995
1297
|
}
|
|
996
1298
|
}
|
|
997
1299
|
|
|
1300
|
+
async function doDelete(ext, btn) {
|
|
1301
|
+
if (!confirm(t("extlist.delete.confirm", { id: ext.id }))) return;
|
|
1302
|
+
btn.disabled = true;
|
|
1303
|
+
try {
|
|
1304
|
+
const res = await fetch("/api/ext/ext-studio/local", {
|
|
1305
|
+
method: "DELETE",
|
|
1306
|
+
headers: { "Content-Type": "application/json" },
|
|
1307
|
+
body: JSON.stringify({ ext_id: ext.id }),
|
|
1308
|
+
});
|
|
1309
|
+
let data = {};
|
|
1310
|
+
try { data = await res.json(); } catch (_e) {}
|
|
1311
|
+
if (!res.ok) throw new Error(data?.error || `Error ${res.status}`);
|
|
1312
|
+
await reload(); rebuild();
|
|
1313
|
+
} catch (e) {
|
|
1314
|
+
alert(t("err.generic", { msg: e.message }));
|
|
1315
|
+
btn.disabled = false;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
998
1319
|
async function doPublish(ext, prevVersion) {
|
|
999
1320
|
const ok = await runPublishFlow(ext, prevVersion);
|
|
1000
1321
|
if (ok) { await reload(); rebuild(); }
|
|
@@ -1304,25 +1625,23 @@
|
|
|
1304
1625
|
.studio-field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px; }
|
|
1305
1626
|
.studio-label { font-size: 12px; font-weight: 500; color: var(--color-text-secondary); }
|
|
1306
1627
|
.studio-select, .studio-textarea { width: 100%; box-sizing: border-box; background: var(--color-bg-input); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); padding: 7px 8px; color: var(--color-text-primary); font-size: 13px; font-family: inherit; }
|
|
1628
|
+
.studio-select { appearance: none; -webkit-appearance: none; padding-right: 28px; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M2 4l4 4 4-4' stroke='%23888' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 8px center; }
|
|
1307
1629
|
.studio-select:focus, .studio-textarea:focus { border-color: var(--color-accent-primary); outline: none; }
|
|
1308
1630
|
.studio-empty { color: var(--color-text-muted); font-size: 12px; margin: 4px 0; }
|
|
1309
1631
|
.studio-hint { color: var(--color-text-tertiary); font-size: 12px; line-height: 1.5; margin: 10px 0 0; }
|
|
1310
1632
|
.studio-detail { border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); padding: 12px; margin-bottom: 14px; background: var(--color-bg-secondary); }
|
|
1311
|
-
.studio-detail-name { margin: 0
|
|
1312
|
-
.studio-detail-desc { margin: 0 0
|
|
1313
|
-
.studio-meta { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; margin: 0 0 10px; font-size: 12px; }
|
|
1314
|
-
.studio-meta dt { color: var(--color-text-tertiary); }
|
|
1315
|
-
.studio-meta dd { margin: 0; color: var(--color-text-primary); }
|
|
1633
|
+
.studio-detail-name { margin: 0; font-size: 14px; color: var(--color-text-primary); }
|
|
1634
|
+
.studio-detail-desc { margin: 4px 0 0; font-size: 12px; color: var(--color-text-secondary); line-height: 1.5; }
|
|
1316
1635
|
.studio-units { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
1317
1636
|
.studio-unit-chip { display: inline-flex; background: var(--color-bg-hover); color: var(--color-text-primary); border: 1px solid var(--color-border-primary); border-radius: var(--radius-sm); padding: 2px 8px; font-size: 12px; }
|
|
1318
|
-
.studio-actions { display: flex; align-items: center; gap: 8px; margin: 12px 0; }
|
|
1637
|
+
.studio-actions { display: flex; align-items: center; gap: 8px; margin: 12px 0 0; }
|
|
1319
1638
|
.studio-skill-promo .studio-actions { margin: 12px 0 0; }
|
|
1320
1639
|
.studio-btn { padding: 7px 14px; border-radius: var(--radius-sm); border: 1px solid var(--color-border-primary); background: transparent; color: var(--color-text-secondary); cursor: pointer; font-size: 13px; font-weight: 500; }
|
|
1321
1640
|
.studio-btn:hover { background: var(--color-bg-hover); color: var(--color-text-primary); }
|
|
1322
1641
|
.studio-btn-primary { background: var(--color-button-primary); color: var(--color-button-primary-text); border-color: transparent; }
|
|
1323
1642
|
.studio-btn-primary:hover { background: var(--color-button-primary-hover); color: var(--color-button-primary-text); }
|
|
1324
1643
|
.studio-btn-primary:disabled { opacity: 0.6; cursor: default; }
|
|
1325
|
-
.studio-btn-danger { color: var(--color-error); border-color: var(--color-error-border);
|
|
1644
|
+
.studio-btn-danger { color: var(--color-error); border-color: var(--color-error-border); }
|
|
1326
1645
|
.studio-btn-danger:hover { background: var(--color-error-bg); color: var(--color-error); }
|
|
1327
1646
|
.studio-btn-ghost { color: var(--color-text-secondary); font-weight: 500; }
|
|
1328
1647
|
.studio-btn-ghost:hover { background: var(--color-bg-hover); color: var(--color-text-primary); }
|
|
@@ -1344,6 +1663,12 @@
|
|
|
1344
1663
|
.studio-feedback-error { color: var(--color-error); }
|
|
1345
1664
|
.studio-feedback-warn { color: var(--color-warning, var(--color-text-secondary)); }
|
|
1346
1665
|
.studio-published { margin-top: 16px; border-top: 1px solid var(--color-border-primary); padding-top: 12px; }
|
|
1666
|
+
@keyframes studio-shimmer { 0% { background-position: -200px 0; } 100% { background-position: calc(200px + 100%) 0; } }
|
|
1667
|
+
.studio-skeleton { display: inline-block; border-radius: 4px; background: linear-gradient(90deg, var(--color-bg-hover) 25%, var(--color-border-primary) 50%, var(--color-bg-hover) 75%); background-size: 200px 100%; animation: studio-shimmer 1.4s infinite; }
|
|
1668
|
+
.studio-skeleton-name { width: 120px; height: 14px; }
|
|
1669
|
+
.studio-skeleton-badge { width: 44px; height: 16px; border-radius: 10px; }
|
|
1670
|
+
.studio-skeleton-ver { width: 36px; height: 12px; }
|
|
1671
|
+
.studio-skeleton-btn { width: 44px; height: 26px; margin-left: auto; border-radius: 6px; }
|
|
1347
1672
|
.studio-published-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 0; font-size: 12px; border-bottom: 1px solid var(--color-border-primary); }
|
|
1348
1673
|
.studio-published-name { display: flex; align-items: center; gap: 6px; color: var(--color-text-primary); }
|
|
1349
1674
|
.studio-page { width: 100%; }
|
|
@@ -1366,8 +1691,8 @@
|
|
|
1366
1691
|
.studio-skill-badge-local { background: var(--color-bg-hover); color: var(--color-text-tertiary); }
|
|
1367
1692
|
.studio-skill-badge-changed { background: var(--color-warning-bg, var(--color-bg-hover)); color: var(--color-warning, var(--color-text-secondary)); }
|
|
1368
1693
|
.studio-skill-badge-shadow { background: var(--color-bg-hover); color: var(--color-text-secondary); }
|
|
1369
|
-
.studio-skill-desc { margin: 6px 0
|
|
1370
|
-
.studio-skill-meta { display: flex; gap: 12px; font-size: 11px; color: var(--color-text-muted); }
|
|
1694
|
+
.studio-skill-desc { margin: 6px 0 6px; font-size: 12px; color: var(--color-text-secondary); line-height: 1.5; }
|
|
1695
|
+
.studio-skill-meta { display: flex; gap: 12px; font-size: 11px; color: var(--color-text-muted); margin-top: 6px; }
|
|
1371
1696
|
.studio-skill-promo { border: 1px solid var(--color-border-primary); border-radius: var(--radius-md, 8px); padding: 16px 18px; margin: 0 0 24px; background: var(--color-bg-secondary); }
|
|
1372
1697
|
.studio-skill-promo-text { margin: 0 0 4px; font-size: 14px; font-weight: 600; color: var(--color-text-primary); }
|
|
1373
1698
|
.studio-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 9999; animation: studio-overlay-in 0.15s ease; }
|
|
@@ -1390,6 +1715,21 @@
|
|
|
1390
1715
|
.studio-modal-link { display: inline-block; font-size: 13px; color: var(--color-accent-primary); }
|
|
1391
1716
|
.studio-modal-footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--color-border-primary); }
|
|
1392
1717
|
.studio-pub-success { margin-right: auto; font-size: 13px; font-weight: 500; color: var(--color-success); }
|
|
1718
|
+
.studio-section { margin-bottom: 20px; }
|
|
1719
|
+
.studio-detail-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 6px; }
|
|
1720
|
+
.studio-info-row { display: flex; align-items: baseline; gap: 8px; margin: 8px 0 0; }
|
|
1721
|
+
.studio-info-label { font-size: 12px; color: var(--color-text-tertiary); flex-shrink: 0; }
|
|
1722
|
+
.studio-info-value { font-size: 13px; color: var(--color-text-primary); }
|
|
1723
|
+
.studio-section-label { margin-top: 10px; margin-bottom: 6px; }
|
|
1724
|
+
.studio-detail-head .studio-detail-name { margin: 0; }
|
|
1725
|
+
.studio-form-status { font-size: 12px; color: var(--color-text-tertiary); }
|
|
1726
|
+
.studio-entry-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 8px; }
|
|
1727
|
+
.studio-entry-row { display: flex; align-items: stretch; gap: 8px; }
|
|
1728
|
+
.studio-entry-arrow { font-size: 12px; color: var(--color-text-tertiary); flex-shrink: 0; }
|
|
1729
|
+
.studio-entry-row .studio-select { flex: 1; width: auto; padding: 6px 28px 6px 8px; }
|
|
1730
|
+
.studio-entry-row .studio-select:focus { border-color: var(--color-accent-primary); outline: none; }
|
|
1731
|
+
.studio-btn-sm { padding: 4px 10px; font-size: 12px; }
|
|
1732
|
+
.studio-entry-row .studio-btn-sm { padding: 0 10px; }
|
|
1393
1733
|
`;
|
|
1394
1734
|
document.head.appendChild(style);
|
|
1395
1735
|
})();
|