openclacky 1.5.13 → 1.5.15
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 +96 -0
- data/lib/clacky/agent/chunk_index.rb +83 -0
- data/lib/clacky/agent/history_navigation.rb +239 -0
- data/lib/clacky/agent/session_serializer.rb +53 -98
- data/lib/clacky/agent.rb +477 -297
- data/lib/clacky/agent_config.rb +5 -3
- data/lib/clacky/billing/billing_store.rb +2 -2
- data/lib/clacky/billing/platform_billing.rb +7 -0
- data/lib/clacky/brand_config.rb +27 -19
- data/lib/clacky/cli.rb +92 -12
- data/lib/clacky/cli_guidance.rb +48 -0
- data/lib/clacky/client.rb +2 -2
- data/lib/clacky/default_extensions/ext-studio/agents/ext-developer/system_prompt.md +70 -134
- data/lib/clacky/default_extensions/ext-studio/api/handler.rb +4 -1
- data/lib/clacky/default_extensions/ext-studio/panels/studio/view.js +307 -98
- data/lib/clacky/default_extensions/ext-studio/skills/ext-develop/SKILL.md +179 -577
- data/lib/clacky/default_extensions/git/panels/git/view.js +34 -14
- data/lib/clacky/default_extensions/preview/ext.yml +20 -0
- data/lib/clacky/default_extensions/preview/panels/preview/view.js +475 -0
- data/lib/clacky/default_extensions/time_machine/panels/time_machine/view.js +1 -10
- data/lib/clacky/extension/api_extension.rb +18 -6
- data/lib/clacky/extension/verifier.rb +1 -1
- data/lib/clacky/json_ui_controller.rb +4 -2
- data/lib/clacky/media/openai_compat.rb +70 -30
- data/lib/clacky/message_format/bedrock.rb +6 -1
- data/lib/clacky/message_format/open_ai.rb +22 -5
- data/lib/clacky/message_format/open_ai_responses.rb +7 -3
- data/lib/clacky/plain_ui_controller.rb +1 -1
- data/lib/clacky/prompts/base.md +1 -1
- data/lib/clacky/providers.rb +44 -13
- data/lib/clacky/rich_ui/components/composer_guidance.rb +49 -0
- data/lib/clacky/rich_ui/rich_ui_controller.rb +24 -4
- data/lib/clacky/rich_ui/shell/rich_agent_shell.rb +13 -0
- data/lib/clacky/search_config.rb +3 -3
- data/lib/clacky/server/channel/channel_manager.rb +25 -7
- data/lib/clacky/server/channel/channel_ui_controller.rb +1 -1
- data/lib/clacky/server/dir_picker.rb +67 -4
- data/lib/clacky/server/git_panel.rb +10 -2
- data/lib/clacky/server/http_server.rb +391 -75
- data/lib/clacky/server/preview.rb +351 -0
- data/lib/clacky/server/session_registry.rb +4 -0
- data/lib/clacky/server/web_ui_controller.rb +10 -5
- data/lib/clacky/session_manager.rb +52 -1
- data/lib/clacky/skill.rb +48 -0
- data/lib/clacky/tools/browser.rb +176 -19
- data/lib/clacky/tools/web_search.rb +91 -8
- data/lib/clacky/ui2/components/command_suggestions.rb +3 -1
- data/lib/clacky/ui2/components/input_area.rb +37 -4
- data/lib/clacky/ui2/components/modal_component.rb +35 -6
- data/lib/clacky/ui2/screen_buffer.rb +1 -0
- data/lib/clacky/ui2/ui_controller.rb +72 -12
- data/lib/clacky/ui_interface.rb +1 -1
- data/lib/clacky/utils/file_processor.rb +19 -7
- data/lib/clacky/utils/mac_app_detector.rb +186 -0
- data/lib/clacky/utils/model_pricing.rb +118 -68
- data/lib/clacky/utils/windows_app_detector.rb +334 -0
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/app.css +998 -168
- data/lib/clacky/web/app.js +19 -0
- data/lib/clacky/web/components/chat-navigator.js +489 -202
- data/lib/clacky/web/components/code-editor.js +191 -5
- data/lib/clacky/web/components/composer.js +3 -1
- data/lib/clacky/web/components/datepicker.js +19 -19
- data/lib/clacky/web/components/mentions.js +11 -14
- data/lib/clacky/web/components/model-picker.js +66 -23
- data/lib/clacky/web/components/quote-select.js +341 -0
- data/lib/clacky/web/core/aside.js +157 -8
- data/lib/clacky/web/core/ext.js +16 -0
- data/lib/clacky/web/features/billing/view.js +75 -21
- data/lib/clacky/web/features/extensions/store.js +4 -1
- data/lib/clacky/web/features/model-tester/store.js +37 -4
- data/lib/clacky/web/features/skills/store.js +18 -1
- data/lib/clacky/web/features/skills/view.js +128 -7
- data/lib/clacky/web/features/workspace/store.js +80 -7
- data/lib/clacky/web/features/workspace/view.js +474 -43
- data/lib/clacky/web/i18n.js +158 -23
- data/lib/clacky/web/index.html +57 -29
- data/lib/clacky/web/sessions.js +517 -127
- data/lib/clacky/web/settings.js +155 -6
- data/lib/clacky/web/utils.js +34 -0
- data/lib/clacky/web/vendor/codemirror/codemirror.min.js +24 -19
- data/lib/clacky/web/vendor/codemirror/entry.js +130 -0
- data/lib/clacky/web/vendor/codemirror/package.json +29 -0
- data/lib/clacky/web/ws-dispatcher.js +161 -4
- data/lib/clacky.rb +2 -0
- metadata +13 -1
|
@@ -46,8 +46,11 @@
|
|
|
46
46
|
"publish.already": "Already published. Enable \"publish a new version\" to ship.",
|
|
47
47
|
"published.title": "Your published extensions",
|
|
48
48
|
"published.empty": "You haven't published anything yet.",
|
|
49
|
+
"published.brandTitle": "Your brand extensions",
|
|
50
|
+
"published.brandEmpty": "You haven't published any brand extensions yet.",
|
|
49
51
|
"btn.unpublish": "Unpublish",
|
|
50
52
|
"published.confirm": "Unpublish {{id}} from the marketplace?",
|
|
53
|
+
"published.brandConfirm": "Unpublish {{id}} from your brand?",
|
|
51
54
|
"err.generic": "Something went wrong: {{msg}}",
|
|
52
55
|
"err.version_conflict": "Version {{ver}} has already been published. Please enter a higher version number above and try again.",
|
|
53
56
|
"err.invalid_version": "Invalid version. Use format like 1.0.0.",
|
|
@@ -133,6 +136,8 @@
|
|
|
133
136
|
"extlist.local.empty": "No local extensions. Create one below.",
|
|
134
137
|
"extlist.badge.published": "Published",
|
|
135
138
|
"extlist.badge.draft": "Draft",
|
|
139
|
+
"extlist.badge.unlisted": "Unlisted",
|
|
140
|
+
"extlist.unlisted.hint": "Taken down by the marketplace. Publishing a new version restores it.",
|
|
136
141
|
"extlist.badge.local": "Not published",
|
|
137
142
|
"extlist.verify.ok": "Checks passed",
|
|
138
143
|
"extlist.verify.errors": "{{n}} error(s)",
|
|
@@ -140,6 +145,7 @@
|
|
|
140
145
|
"extlist.btn.publish": "Publish to Marketplace",
|
|
141
146
|
"extlist.btn.publishBrand": "Publish to Brand",
|
|
142
147
|
"extlist.btn.update": "Update",
|
|
148
|
+
"extlist.btn.updateBrand": "Update Brand Version",
|
|
143
149
|
"extlist.btn.pack": "Pack (.zip)",
|
|
144
150
|
"extlist.btn.packing": "Packing…",
|
|
145
151
|
"extlist.btn.unpublish": "Unpublish",
|
|
@@ -147,12 +153,14 @@
|
|
|
147
153
|
"extlist.btn.disable": "Disable",
|
|
148
154
|
"extlist.btn.enable": "Enable",
|
|
149
155
|
"extlist.btn.delete": "Delete",
|
|
156
|
+
"extlist.btn.more": "More actions",
|
|
150
157
|
"extlist.badge.disabled": "Disabled",
|
|
151
158
|
"extlist.delete.confirm": "Delete local extension \"{{id}}\"? This cannot be undone.",
|
|
152
159
|
"extlist.iterate.seed": "Iterate on extension {{id}}",
|
|
153
160
|
"extlist.changelog.prompt": "Changelog (optional):",
|
|
154
161
|
"extlist.overwrite.confirm": "\"{{id}}\" is already published. Publish a new version?",
|
|
155
162
|
"extlist.unpublish.confirm": "Unpublish {{id}} from the marketplace?",
|
|
163
|
+
"extlist.unpublishBrand.confirm": "Unpublish {{id}} from your brand?",
|
|
156
164
|
"extlist.publishing": "Publishing…",
|
|
157
165
|
"extlist.needLicense": "Publishing requires an activated user license.",
|
|
158
166
|
"extlist.brandLocked": "Publishing to the marketplace requires a creator license.",
|
|
@@ -244,8 +252,11 @@
|
|
|
244
252
|
"publish.already": "已发布过。勾选「发布新版本」后再试。",
|
|
245
253
|
"published.title": "你发布的扩展",
|
|
246
254
|
"published.empty": "你还没有发布任何扩展。",
|
|
255
|
+
"published.brandTitle": "你发布到品牌的扩展",
|
|
256
|
+
"published.brandEmpty": "你还没有发布到品牌的扩展。",
|
|
247
257
|
"btn.unpublish": "下架",
|
|
248
258
|
"published.confirm": "确定要从市场下架 {{id}} 吗?",
|
|
259
|
+
"published.brandConfirm": "确定要从品牌下架 {{id}} 吗?",
|
|
249
260
|
"err.generic": "出错了:{{msg}}",
|
|
250
261
|
"err.version_conflict": "版本 {{ver}} 已发布过,请在上方输入更高的版本号后重试。",
|
|
251
262
|
"err.invalid_version": "版本号格式不对,请使用如 1.0.0 的格式。",
|
|
@@ -331,6 +342,8 @@
|
|
|
331
342
|
"extlist.local.empty": "本地暂无扩展,在下方新建一个。",
|
|
332
343
|
"extlist.badge.published": "已发布",
|
|
333
344
|
"extlist.badge.draft": "草稿",
|
|
345
|
+
"extlist.badge.unlisted": "已下架",
|
|
346
|
+
"extlist.unlisted.hint": "已被市场下架,重新发布新版本可恢复上架。",
|
|
334
347
|
"extlist.badge.local": "未发布",
|
|
335
348
|
"extlist.verify.ok": "检查通过",
|
|
336
349
|
"extlist.verify.errors": "{{n}} 个错误",
|
|
@@ -338,6 +351,7 @@
|
|
|
338
351
|
"extlist.btn.publish": "发布到市场",
|
|
339
352
|
"extlist.btn.publishBrand": "发布到品牌",
|
|
340
353
|
"extlist.btn.update": "更新到市场",
|
|
354
|
+
"extlist.btn.updateBrand": "更新到品牌",
|
|
341
355
|
"extlist.btn.pack": "打包(.zip)",
|
|
342
356
|
"extlist.btn.packing": "打包中…",
|
|
343
357
|
"extlist.btn.unpublish": "下架",
|
|
@@ -345,12 +359,14 @@
|
|
|
345
359
|
"extlist.btn.disable": "禁止",
|
|
346
360
|
"extlist.btn.enable": "启用",
|
|
347
361
|
"extlist.btn.delete": "删除",
|
|
362
|
+
"extlist.btn.more": "更多操作",
|
|
348
363
|
"extlist.badge.disabled": "已禁用",
|
|
349
364
|
"extlist.delete.confirm": "确定要删除本地扩展「{{id}}」吗?此操作不可恢复。",
|
|
350
365
|
"extlist.iterate.seed": "迭代扩展 {{id}}",
|
|
351
366
|
"extlist.changelog.prompt": "更新说明(可选):",
|
|
352
367
|
"extlist.overwrite.confirm": "「{{id}}」已经发布过了。要发布新版本吗?",
|
|
353
368
|
"extlist.unpublish.confirm": "确定要从市场下架 {{id}} 吗?",
|
|
369
|
+
"extlist.unpublishBrand.confirm": "确定要从品牌下架 {{id}} 吗?",
|
|
354
370
|
"extlist.publishing": "发布中…",
|
|
355
371
|
"extlist.needLicense": "发布需要已激活的用户授权。",
|
|
356
372
|
"extlist.brandLocked": "发布到市场需要创作者权限。",
|
|
@@ -413,6 +429,10 @@
|
|
|
413
429
|
return str;
|
|
414
430
|
}
|
|
415
431
|
|
|
432
|
+
function isBrandOwner() {
|
|
433
|
+
return typeof Brand !== "undefined" && Brand.branded && Brand.userLicensed;
|
|
434
|
+
}
|
|
435
|
+
|
|
416
436
|
function api(path) { return `/api/ext/ext-studio${path}`; }
|
|
417
437
|
|
|
418
438
|
function toast(msg, type) {
|
|
@@ -501,6 +521,76 @@
|
|
|
501
521
|
return node;
|
|
502
522
|
}
|
|
503
523
|
|
|
524
|
+
// ── Shared: overflow menu ──────────────────────────────────────────────
|
|
525
|
+
// Mirrors the session list's "⋯" menu so buttons collapsed on narrow screens
|
|
526
|
+
// stay reachable. Items proxy the real buttons rather than duplicating them,
|
|
527
|
+
// which keeps transient label/disabled state (e.g. "Packing…") in sync.
|
|
528
|
+
const OVERFLOW_ICON = `<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="2.5" cy="7" r="1.2" fill="currentColor"/><circle cx="7" cy="7" r="1.2" fill="currentColor"/><circle cx="11.5" cy="7" r="1.2" fill="currentColor"/></svg>`;
|
|
529
|
+
|
|
530
|
+
let overflowDismiss = null;
|
|
531
|
+
let overflowAnchor = null;
|
|
532
|
+
|
|
533
|
+
function closeOverflowMenu() {
|
|
534
|
+
if (overflowDismiss) {
|
|
535
|
+
document.removeEventListener("click", overflowDismiss, true);
|
|
536
|
+
overflowDismiss = null;
|
|
537
|
+
}
|
|
538
|
+
overflowAnchor = null;
|
|
539
|
+
const existing = document.querySelector(".studio-overflow-menu");
|
|
540
|
+
if (existing) existing.remove();
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function openOverflowMenu(anchor, buttons) {
|
|
544
|
+
// Re-tapping the same "⋯" toggles; tapping a different card must switch to it
|
|
545
|
+
// rather than merely dismissing the menu that is already open.
|
|
546
|
+
if (overflowAnchor === anchor) { closeOverflowMenu(); return; }
|
|
547
|
+
closeOverflowMenu();
|
|
548
|
+
overflowAnchor = anchor;
|
|
549
|
+
const menu = el("div", { class: "session-actions-menu studio-overflow-menu" });
|
|
550
|
+
buttons.forEach((btn) => {
|
|
551
|
+
const item = el("div", { class: "session-actions-menu-item" }, [
|
|
552
|
+
el("span", { class: "session-actions-menu-label", text: btn.textContent }),
|
|
553
|
+
]);
|
|
554
|
+
if (btn.classList.contains("studio-btn-danger")) item.classList.add("session-actions-menu-item--danger");
|
|
555
|
+
if (btn.disabled) {
|
|
556
|
+
item.classList.add("is-disabled");
|
|
557
|
+
const tip = btn.getAttribute("data-tooltip");
|
|
558
|
+
if (tip) item.setAttribute("data-tooltip", tip);
|
|
559
|
+
} else {
|
|
560
|
+
item.addEventListener("click", () => { closeOverflowMenu(); btn.click(); });
|
|
561
|
+
}
|
|
562
|
+
menu.appendChild(item);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Take the menu out of normal flow before measuring: as a static block child
|
|
566
|
+
// of <body> it would stretch to the full body width, so mw would be the
|
|
567
|
+
// viewport width and the clamp below would fling the menu to the far edge.
|
|
568
|
+
menu.style.visibility = "hidden";
|
|
569
|
+
menu.style.position = "fixed";
|
|
570
|
+
menu.style.top = "0px";
|
|
571
|
+
menu.style.left = "0px";
|
|
572
|
+
document.body.appendChild(menu);
|
|
573
|
+
const rect = anchor.getBoundingClientRect();
|
|
574
|
+
const mw = menu.offsetWidth;
|
|
575
|
+
const mh = menu.offsetHeight;
|
|
576
|
+
let top = rect.bottom + 4;
|
|
577
|
+
let left = rect.left;
|
|
578
|
+
if (top + mh > window.innerHeight - 8) top = Math.max(8, rect.top - mh - 4);
|
|
579
|
+
if (left + mw > window.innerWidth - 8) left = Math.max(8, window.innerWidth - mw - 8);
|
|
580
|
+
menu.style.top = top + "px";
|
|
581
|
+
menu.style.left = left + "px";
|
|
582
|
+
menu.style.visibility = "";
|
|
583
|
+
|
|
584
|
+
// Capture phase runs before the anchor's own handler, so the anchor has to be
|
|
585
|
+
// excluded here or reopening would immediately close and reopen the menu.
|
|
586
|
+
overflowDismiss = (e) => {
|
|
587
|
+
const node = e.target instanceof Element ? e.target : null;
|
|
588
|
+
if (node && (node.closest(".studio-overflow-menu") || node.closest(".studio-actions-overflow"))) return;
|
|
589
|
+
closeOverflowMenu();
|
|
590
|
+
};
|
|
591
|
+
document.addEventListener("click", overflowDismiss, true);
|
|
592
|
+
}
|
|
593
|
+
|
|
504
594
|
// ── Shared: modal dialog ───────────────────────────────────────────────
|
|
505
595
|
// A minimal overlay modal used by the publish-confirm and device-binding
|
|
506
596
|
// flows. Returns the body element so callers can drive it live (bind flow),
|
|
@@ -1412,37 +1502,50 @@
|
|
|
1412
1502
|
async function loadPublished() {
|
|
1413
1503
|
const box = container.querySelector(".studio-published");
|
|
1414
1504
|
if (!box) return;
|
|
1505
|
+
const brandOwner = isBrandOwner();
|
|
1506
|
+
const titleKey = brandOwner ? "published.brandTitle" : "published.title";
|
|
1507
|
+
const emptyKey = brandOwner ? "published.brandEmpty" : "published.empty";
|
|
1508
|
+
const confirmKey = brandOwner ? "published.brandConfirm" : "published.confirm";
|
|
1415
1509
|
box.innerHTML = "";
|
|
1416
|
-
box.appendChild(el("div", { class: "studio-label", text: t(
|
|
1510
|
+
box.appendChild(el("div", { class: "studio-label", text: t(titleKey) }));
|
|
1417
1511
|
renderPublishedSkeleton(box);
|
|
1418
1512
|
try {
|
|
1419
|
-
const
|
|
1513
|
+
const endpoint = brandOwner ? "/published_brand" : "/published";
|
|
1514
|
+
const data = await getJson(endpoint);
|
|
1420
1515
|
box.innerHTML = "";
|
|
1421
|
-
box.appendChild(el("div", { class: "studio-label", text: t(
|
|
1422
|
-
const exts =
|
|
1423
|
-
|
|
1516
|
+
box.appendChild(el("div", { class: "studio-label", text: t(titleKey) }));
|
|
1517
|
+
const exts = brandOwner
|
|
1518
|
+
? (data.extensions || [])
|
|
1519
|
+
: (data.extensions || []).filter((e) => e.origin !== "self");
|
|
1520
|
+
if (!exts.length) { box.appendChild(el("p", { class: "studio-empty", text: t(emptyKey) })); return; }
|
|
1424
1521
|
exts.forEach((e) => {
|
|
1425
1522
|
const card = el("div", { class: "studio-skill-card" });
|
|
1426
1523
|
const head = el("div", { class: "studio-skill-head" });
|
|
1524
|
+
const unlisted = listingState(e) === "unlisted";
|
|
1427
1525
|
head.appendChild(el("span", { class: "studio-skill-name", text: e.name || e.id }));
|
|
1428
|
-
const
|
|
1429
|
-
|
|
1526
|
+
const stateBadge = el("span", { class: `studio-skill-badge studio-skill-badge-${listingBadgeKind(e)}`, text: listingBadgeText(e) });
|
|
1527
|
+
if (unlisted) stateBadge.setAttribute("data-tooltip", t("extlist.unlisted.hint"));
|
|
1528
|
+
head.appendChild(stateBadge);
|
|
1430
1529
|
if (e.version) head.appendChild(el("span", { style: "font-size:11px;color:var(--color-text-muted);", text: "v" + e.version }));
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
unpubBtn.
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1530
|
+
// A taken-down extension has nothing left to unpublish — the only way
|
|
1531
|
+
// back on the marketplace is shipping a new version.
|
|
1532
|
+
if ((!brandOwner || e.origin === "self") && !unlisted) {
|
|
1533
|
+
const unpubBtn = el("button", { class: "studio-btn studio-btn-danger studio-btn-sm", text: t("btn.unpublish") });
|
|
1534
|
+
unpubBtn.style.marginLeft = "auto";
|
|
1535
|
+
unpubBtn.addEventListener("click", async () => {
|
|
1536
|
+
if (!await Clacky.Modal.confirm(t(confirmKey, { id: e.id }))) return;
|
|
1537
|
+
unpubBtn.disabled = true;
|
|
1538
|
+
try { await postJson("/unpublish", { ext_id: e.id }); loadPublished(); }
|
|
1539
|
+
catch (err) { unpubBtn.disabled = false; feedback(t("err.generic", { msg: err.message }), "error"); }
|
|
1540
|
+
});
|
|
1541
|
+
head.appendChild(unpubBtn);
|
|
1542
|
+
}
|
|
1440
1543
|
card.appendChild(head);
|
|
1441
1544
|
box.appendChild(card);
|
|
1442
1545
|
});
|
|
1443
1546
|
} catch (e) {
|
|
1444
1547
|
box.innerHTML = "";
|
|
1445
|
-
box.appendChild(el("div", { class: "studio-label", text: t(
|
|
1548
|
+
box.appendChild(el("div", { class: "studio-label", text: t(titleKey) }));
|
|
1446
1549
|
box.appendChild(el("p", { class: "studio-empty", text: t("err.generic", { msg: e.message }) }));
|
|
1447
1550
|
}
|
|
1448
1551
|
}
|
|
@@ -1467,13 +1570,14 @@
|
|
|
1467
1570
|
));
|
|
1468
1571
|
|
|
1469
1572
|
const bar = el("div", { class: "studio-actions" });
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1573
|
+
if (isBrandOwner()) {
|
|
1574
|
+
bar.appendChild(el("button", { class: "studio-btn studio-btn-primary studio-publish-brand-btn", text: t("btn.publishBrand"), onclick: doPublishBrand }));
|
|
1575
|
+
} else {
|
|
1576
|
+
const brandNonAdmin = typeof Brand !== "undefined" && Brand.branded && !Brand.userLicensed;
|
|
1577
|
+
const pubBtn = el("button", { class: "studio-btn studio-btn-primary studio-publish-btn", text: t("btn.publish"), onclick: doPublish });
|
|
1578
|
+
pubBtn.disabled = brandNonAdmin;
|
|
1579
|
+
if (brandNonAdmin) pubBtn.setAttribute("data-tooltip", t("extlist.brandLocked"));
|
|
1580
|
+
bar.appendChild(pubBtn);
|
|
1477
1581
|
}
|
|
1478
1582
|
const packBtn = el("button", { class: "studio-btn studio-btn-ghost studio-pack-btn", text: t("btn.pack"), onclick: doPack });
|
|
1479
1583
|
bar.appendChild(packBtn);
|
|
@@ -1497,6 +1601,47 @@
|
|
|
1497
1601
|
};
|
|
1498
1602
|
}
|
|
1499
1603
|
|
|
1604
|
+
// Cloud / local (plus brand, for extensions) lists as a segmented control
|
|
1605
|
+
// instead of stacked sections — one list visible at a time. `state.active`
|
|
1606
|
+
// lives with the caller so the selection survives a rebuild.
|
|
1607
|
+
function sectionTabs(sections, state) {
|
|
1608
|
+
const bar = el("div", { class: "studio-seg-bar" });
|
|
1609
|
+
const seg = el("div", { class: "segmented-control" });
|
|
1610
|
+
const hint = el("span", { class: "studio-seg-hint" });
|
|
1611
|
+
const listBox = el("div", { class: "studio-seg-body" });
|
|
1612
|
+
bar.appendChild(seg);
|
|
1613
|
+
bar.appendChild(hint);
|
|
1614
|
+
|
|
1615
|
+
if (!sections.some((s) => s.id === state.active)) state.active = sections[0].id;
|
|
1616
|
+
|
|
1617
|
+
function paint() {
|
|
1618
|
+
const current = sections.filter((s) => s.id === state.active)[0] || sections[0];
|
|
1619
|
+
seg.innerHTML = "";
|
|
1620
|
+
sections.forEach((s) => {
|
|
1621
|
+
const btn = el("button", {
|
|
1622
|
+
class: "segmented-control__btn" + (s.id === current.id ? " active" : ""),
|
|
1623
|
+
text: s.label,
|
|
1624
|
+
});
|
|
1625
|
+
btn.addEventListener("click", () => {
|
|
1626
|
+
if (state.active === s.id) return;
|
|
1627
|
+
state.active = s.id;
|
|
1628
|
+
paint();
|
|
1629
|
+
});
|
|
1630
|
+
seg.appendChild(btn);
|
|
1631
|
+
});
|
|
1632
|
+
hint.textContent = current.hint;
|
|
1633
|
+
listBox.innerHTML = "";
|
|
1634
|
+
if (!current.items.length) {
|
|
1635
|
+
listBox.appendChild(el("p", { class: "studio-empty", text: t(current.emptyKey) }));
|
|
1636
|
+
} else {
|
|
1637
|
+
current.items.forEach((item) => listBox.appendChild(current.cardFn(item)));
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
paint();
|
|
1641
|
+
|
|
1642
|
+
return [bar, listBox];
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1500
1645
|
function _skeletonHtml() {
|
|
1501
1646
|
return Array.from({ length: 3 }).map(() => `
|
|
1502
1647
|
<div class="studio-skill-card" style="pointer-events:none">
|
|
@@ -1514,6 +1659,29 @@
|
|
|
1514
1659
|
</div>`).join("");
|
|
1515
1660
|
}
|
|
1516
1661
|
|
|
1662
|
+
// An admin takedown keeps `status: "published"` on the platform record —
|
|
1663
|
+
// only hub_status flips (hidden/removed/quarantined). Map both fields to one
|
|
1664
|
+
// badge state so a taken-down extension never reads as live.
|
|
1665
|
+
function listingState(ext) {
|
|
1666
|
+
if (!ext) return "published";
|
|
1667
|
+
if (ext.status === "draft") return "draft";
|
|
1668
|
+
const hub = ext.hub_status;
|
|
1669
|
+
if (hub && hub !== "active") return "unlisted";
|
|
1670
|
+
return "published";
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
function listingBadgeKind(ext) {
|
|
1674
|
+
const state = listingState(ext);
|
|
1675
|
+
if (state === "draft") return "local";
|
|
1676
|
+
return state === "unlisted" ? "unlisted" : "published";
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
function listingBadgeText(ext) {
|
|
1680
|
+
const state = listingState(ext);
|
|
1681
|
+
if (state === "draft") return t("extlist.badge.draft");
|
|
1682
|
+
return state === "unlisted" ? t("extlist.badge.unlisted") : t("extlist.badge.published");
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1517
1685
|
// ── Extensions tab (full-page): cloud + local extension cards ──────────────
|
|
1518
1686
|
// Reuses backend endpoints GET /published (cloud), the shared `store` (local,
|
|
1519
1687
|
// GET /extensions), POST /publish|/pack|/unpublish, and POST /develop to open
|
|
@@ -1524,10 +1692,7 @@
|
|
|
1524
1692
|
let brandCloud = [];
|
|
1525
1693
|
let marketCloud = [];
|
|
1526
1694
|
let brandPublished = [];
|
|
1527
|
-
|
|
1528
|
-
function licensed() {
|
|
1529
|
-
return !(typeof Brand !== "undefined" && Brand.branded && !Brand.userLicensed);
|
|
1530
|
-
}
|
|
1695
|
+
const sectionState = { active: null };
|
|
1531
1696
|
|
|
1532
1697
|
async function reload() {
|
|
1533
1698
|
await store.reload();
|
|
@@ -1548,7 +1713,7 @@
|
|
|
1548
1713
|
brandCloud = [];
|
|
1549
1714
|
}
|
|
1550
1715
|
marketCloud = cloud.filter((e) => e.origin !== "self");
|
|
1551
|
-
brandPublished = cloud.filter((e) => e.origin === "self");
|
|
1716
|
+
brandPublished = isBrandOwner() ? brandCloud : cloud.filter((e) => e.origin === "self");
|
|
1552
1717
|
}
|
|
1553
1718
|
|
|
1554
1719
|
function createExtension(idea) {
|
|
@@ -1559,8 +1724,10 @@
|
|
|
1559
1724
|
.catch((e) => alert(t("err.generic", { msg: e.message })));
|
|
1560
1725
|
}
|
|
1561
1726
|
|
|
1562
|
-
function badge(text, kind) {
|
|
1563
|
-
|
|
1727
|
+
function badge(text, kind, tooltip) {
|
|
1728
|
+
const node = el("span", { class: "studio-skill-badge studio-skill-badge-" + kind, text });
|
|
1729
|
+
if (tooltip) node.setAttribute("data-tooltip", tooltip);
|
|
1730
|
+
return node;
|
|
1564
1731
|
}
|
|
1565
1732
|
|
|
1566
1733
|
function formatUnitCounts(counts) {
|
|
@@ -1575,9 +1742,9 @@
|
|
|
1575
1742
|
function cloudCard(ext) {
|
|
1576
1743
|
const card = el("div", { class: "studio-skill-card" });
|
|
1577
1744
|
const head = el("div", { class: "studio-skill-head" });
|
|
1745
|
+
const unlisted = listingState(ext) === "unlisted";
|
|
1578
1746
|
head.appendChild(el("span", { class: "studio-skill-name", text: ext.name || ext.id }));
|
|
1579
|
-
|
|
1580
|
-
head.appendChild(badge(isDraft ? t("extlist.badge.draft") : t("extlist.badge.published"), isDraft ? "local" : "published"));
|
|
1747
|
+
head.appendChild(badge(listingBadgeText(ext), listingBadgeKind(ext), unlisted ? t("extlist.unlisted.hint") : null));
|
|
1581
1748
|
card.appendChild(head);
|
|
1582
1749
|
|
|
1583
1750
|
const meta = el("div", { class: "studio-skill-meta" });
|
|
@@ -1586,27 +1753,37 @@
|
|
|
1586
1753
|
|
|
1587
1754
|
const actions = el("div", { class: "studio-actions" });
|
|
1588
1755
|
actions.appendChild(el("button", { class: "studio-btn studio-btn-primary", text: t("extlist.btn.iterate"), onclick: () => createExtension(t("extlist.iterate.seed", { id: ext.id })) }));
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1756
|
+
// A taken-down extension has nothing left to unpublish — the only way
|
|
1757
|
+
// back on the marketplace is shipping a new version.
|
|
1758
|
+
const canUnpublish = (!isBrandOwner() || ext.origin === "self") && !unlisted;
|
|
1759
|
+
if (canUnpublish) {
|
|
1760
|
+
const un = el("button", { class: "studio-btn studio-btn-ghost", text: t("extlist.btn.unpublish") });
|
|
1761
|
+
un.addEventListener("click", async () => {
|
|
1762
|
+
const confirmKey = ext.origin === "self" ? "extlist.unpublishBrand.confirm" : "extlist.unpublish.confirm";
|
|
1763
|
+
if (!await Clacky.Modal.confirm(t(confirmKey, { id: ext.id }))) return;
|
|
1764
|
+
un.disabled = true;
|
|
1765
|
+
try { await postJson("/unpublish", { ext_id: ext.id }); await reload(); rebuild(); }
|
|
1766
|
+
catch (e) { un.disabled = false; alert(t("err.generic", { msg: e.message })); }
|
|
1767
|
+
});
|
|
1768
|
+
actions.appendChild(un);
|
|
1769
|
+
}
|
|
1597
1770
|
card.appendChild(actions);
|
|
1598
1771
|
return card;
|
|
1599
1772
|
}
|
|
1600
1773
|
|
|
1601
1774
|
function localCard(ext) {
|
|
1775
|
+
const brandOwner = isBrandOwner();
|
|
1602
1776
|
const cloudEntry = cloud.find((c) => c.id === ext.id);
|
|
1603
1777
|
const marketEntry = marketCloud.find((c) => c.id === ext.id);
|
|
1604
|
-
const
|
|
1778
|
+
const brandEntry = brandCloud.find((c) => c.id === ext.id);
|
|
1779
|
+
const published = brandOwner ? !!brandEntry : !!marketEntry;
|
|
1780
|
+
const publishedAnywhere = !!cloudEntry;
|
|
1605
1781
|
const marketPublished = !!marketEntry;
|
|
1606
1782
|
const card = el("div", { class: "studio-skill-card" });
|
|
1607
1783
|
const head = el("div", { class: "studio-skill-head" });
|
|
1608
1784
|
head.appendChild(el("span", { class: "studio-skill-name", text: `${ext.name} (${ext.id})` }));
|
|
1609
|
-
|
|
1785
|
+
const publishedEntry = brandOwner ? brandEntry : marketEntry;
|
|
1786
|
+
head.appendChild(published ? badge(listingBadgeText(publishedEntry), listingBadgeKind(publishedEntry)) : badge(t("extlist.badge.local"), "local"));
|
|
1610
1787
|
if (ext.disabled) {
|
|
1611
1788
|
head.appendChild(badge(t("extlist.badge.disabled"), "changed"));
|
|
1612
1789
|
} else if (ext.error_count) {
|
|
@@ -1626,41 +1803,51 @@
|
|
|
1626
1803
|
card.appendChild(meta);
|
|
1627
1804
|
|
|
1628
1805
|
const actions = el("div", { class: "studio-actions" });
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
pub.addEventListener("click", () => doPublish(ext, marketEntry ? marketEntry.version : null));
|
|
1635
|
-
actions.appendChild(pub);
|
|
1636
|
-
|
|
1637
|
-
if (typeof Brand !== "undefined" && Brand.userLicensed) {
|
|
1638
|
-
const brandEntry = brandCloud.find((c) => c.id === ext.id);
|
|
1639
|
-
const brandPub = el("button", { class: "studio-btn", text: t("extlist.btn.publishBrand") });
|
|
1806
|
+
if (brandOwner) {
|
|
1807
|
+
const brandPub = el("button", {
|
|
1808
|
+
class: "studio-btn studio-btn-primary",
|
|
1809
|
+
text: brandEntry ? t("extlist.btn.updateBrand") : t("extlist.btn.publishBrand"),
|
|
1810
|
+
});
|
|
1640
1811
|
brandPub.disabled = !!ext.error_count;
|
|
1641
1812
|
if (ext.error_count) brandPub.setAttribute("data-tooltip", t("extlist.verify.errors", { n: ext.error_count }));
|
|
1642
1813
|
brandPub.addEventListener("click", () => doPublish(ext, brandEntry ? brandEntry.version : null, "self"));
|
|
1643
1814
|
actions.appendChild(brandPub);
|
|
1815
|
+
} else {
|
|
1816
|
+
const brandNonAdmin = typeof Brand !== "undefined" && Brand.branded && !Brand.userLicensed;
|
|
1817
|
+
const pub = el("button", { class: "studio-btn studio-btn-primary", text: marketPublished ? t("extlist.btn.update") : t("extlist.btn.publish") });
|
|
1818
|
+
pub.disabled = !!ext.error_count || brandNonAdmin;
|
|
1819
|
+
const pubTip = ext.error_count ? t("extlist.verify.errors", { n: ext.error_count }) : (brandNonAdmin ? t("extlist.brandLocked") : "");
|
|
1820
|
+
if (pubTip) pub.setAttribute("data-tooltip", pubTip);
|
|
1821
|
+
pub.addEventListener("click", () => doPublish(ext, marketEntry ? marketEntry.version : null));
|
|
1822
|
+
actions.appendChild(pub);
|
|
1644
1823
|
}
|
|
1645
1824
|
|
|
1646
1825
|
actions.appendChild(el("button", { class: "studio-btn", text: t("extlist.btn.iterate"), onclick: () => createExtension(t("extlist.iterate.seed", { id: ext.id })) }));
|
|
1647
|
-
const packBtn = el("button", { class: "studio-btn studio-btn-ghost", text: t("extlist.btn.pack") });
|
|
1826
|
+
const packBtn = el("button", { class: "studio-btn studio-btn-ghost studio-btn-collapsible", text: t("extlist.btn.pack") });
|
|
1648
1827
|
packBtn.addEventListener("click", () => doPack(ext, packBtn));
|
|
1649
1828
|
actions.appendChild(packBtn);
|
|
1650
1829
|
|
|
1651
1830
|
const toggleBtn = el("button", {
|
|
1652
|
-
class: "studio-btn studio-btn-ghost",
|
|
1831
|
+
class: "studio-btn studio-btn-ghost studio-btn-collapsible",
|
|
1653
1832
|
text: ext.disabled ? t("extlist.btn.enable") : t("extlist.btn.disable"),
|
|
1654
1833
|
});
|
|
1655
1834
|
toggleBtn.addEventListener("click", () => doToggleDisabled(ext, toggleBtn));
|
|
1656
1835
|
actions.appendChild(toggleBtn);
|
|
1657
1836
|
card.appendChild(actions);
|
|
1658
1837
|
|
|
1659
|
-
if (!
|
|
1660
|
-
const delBtn = el("button", { class: "studio-btn studio-btn-danger", text: t("extlist.btn.delete") });
|
|
1838
|
+
if (!publishedAnywhere) {
|
|
1839
|
+
const delBtn = el("button", { class: "studio-btn studio-btn-danger studio-btn-collapsible", text: t("extlist.btn.delete") });
|
|
1661
1840
|
delBtn.addEventListener("click", () => doDelete(ext, delBtn));
|
|
1662
1841
|
actions.appendChild(delBtn);
|
|
1663
1842
|
}
|
|
1843
|
+
|
|
1844
|
+
const overflow = el("button", { class: "studio-btn studio-actions-overflow", "aria-label": t("extlist.btn.more"), title: t("extlist.btn.more") });
|
|
1845
|
+
overflow.innerHTML = OVERFLOW_ICON;
|
|
1846
|
+
overflow.addEventListener("click", (e) => {
|
|
1847
|
+
e.stopPropagation();
|
|
1848
|
+
openOverflowMenu(overflow, Array.from(actions.querySelectorAll(".studio-btn-collapsible")));
|
|
1849
|
+
});
|
|
1850
|
+
actions.appendChild(overflow);
|
|
1664
1851
|
return card;
|
|
1665
1852
|
}
|
|
1666
1853
|
|
|
@@ -1714,20 +1901,6 @@
|
|
|
1714
1901
|
if (ok) { await reload(); rebuild(); }
|
|
1715
1902
|
}
|
|
1716
1903
|
|
|
1717
|
-
function section(titleKey, hintKey, items, cardFn, emptyKey) {
|
|
1718
|
-
const box = el("div", { class: "studio-skill-section" });
|
|
1719
|
-
const head = el("div", { class: "studio-skill-section-head" });
|
|
1720
|
-
head.appendChild(el("span", { class: "studio-label", text: t(titleKey) }));
|
|
1721
|
-
head.appendChild(el("span", { class: "studio-skill-hint", text: t(hintKey) }));
|
|
1722
|
-
box.appendChild(head);
|
|
1723
|
-
if (!items.length) {
|
|
1724
|
-
box.appendChild(el("p", { class: "studio-empty", text: t(emptyKey) }));
|
|
1725
|
-
} else {
|
|
1726
|
-
items.forEach((s) => box.appendChild(cardFn(s)));
|
|
1727
|
-
}
|
|
1728
|
-
return box;
|
|
1729
|
-
}
|
|
1730
|
-
|
|
1731
1904
|
function rebuild(headerActions) {
|
|
1732
1905
|
if (!container) return;
|
|
1733
1906
|
container.innerHTML = "";
|
|
@@ -1745,11 +1918,21 @@
|
|
|
1745
1918
|
headerActions.appendChild(newBtn);
|
|
1746
1919
|
}
|
|
1747
1920
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1921
|
+
const sections = [{
|
|
1922
|
+
id: "local",
|
|
1923
|
+
label: t("extlist.section.local"),
|
|
1924
|
+
hint: t("extlist.section.localHint"),
|
|
1925
|
+
items: store.extensions,
|
|
1926
|
+
cardFn: localCard,
|
|
1927
|
+
emptyKey: "extlist.local.empty",
|
|
1928
|
+
}];
|
|
1929
|
+
if (!isBrandOwner()) {
|
|
1930
|
+
sections.push({ id: "cloud", label: t("extlist.section.cloud"), hint: t("extlist.section.cloudHint"), items: marketCloud, cardFn: cloudCard, emptyKey: "extlist.cloud.empty" });
|
|
1751
1931
|
}
|
|
1752
|
-
|
|
1932
|
+
if (brandPublished.length > 0 || isBrandOwner()) {
|
|
1933
|
+
sections.push({ id: "brand", label: t("extlist.section.brand"), hint: t("extlist.section.brandHint"), items: brandPublished, cardFn: cloudCard, emptyKey: "extlist.brand.empty" });
|
|
1934
|
+
}
|
|
1935
|
+
sectionTabs(sections, sectionState).forEach((node) => container.appendChild(node));
|
|
1753
1936
|
}
|
|
1754
1937
|
|
|
1755
1938
|
return {
|
|
@@ -1772,6 +1955,7 @@
|
|
|
1772
1955
|
let cloud = [];
|
|
1773
1956
|
let local = [];
|
|
1774
1957
|
let locked = false;
|
|
1958
|
+
const sectionState = { active: null };
|
|
1775
1959
|
|
|
1776
1960
|
async function reload() {
|
|
1777
1961
|
const r = await getHost("/api/creator/skills");
|
|
@@ -1878,20 +2062,6 @@
|
|
|
1878
2062
|
}
|
|
1879
2063
|
}
|
|
1880
2064
|
|
|
1881
|
-
function section(titleKey, hintKey, items, cardFn, emptyKey) {
|
|
1882
|
-
const box = el("div", { class: "studio-skill-section" });
|
|
1883
|
-
const head = el("div", { class: "studio-skill-section-head" });
|
|
1884
|
-
head.appendChild(el("span", { class: "studio-label", text: t(titleKey) }));
|
|
1885
|
-
head.appendChild(el("span", { class: "studio-skill-hint", text: t(hintKey) }));
|
|
1886
|
-
box.appendChild(head);
|
|
1887
|
-
if (!items.length) {
|
|
1888
|
-
box.appendChild(el("p", { class: "studio-empty", text: t(emptyKey) }));
|
|
1889
|
-
} else {
|
|
1890
|
-
items.forEach((s) => box.appendChild(cardFn(s)));
|
|
1891
|
-
}
|
|
1892
|
-
return box;
|
|
1893
|
-
}
|
|
1894
|
-
|
|
1895
2065
|
function rebuild(headerActions) {
|
|
1896
2066
|
if (!container) return;
|
|
1897
2067
|
container.innerHTML = "";
|
|
@@ -1914,9 +2084,22 @@
|
|
|
1914
2084
|
container.appendChild(lockHint);
|
|
1915
2085
|
}
|
|
1916
2086
|
|
|
1917
|
-
const
|
|
1918
|
-
|
|
1919
|
-
|
|
2087
|
+
const sections = [{
|
|
2088
|
+
id: "local",
|
|
2089
|
+
label: t("skills.section.local"),
|
|
2090
|
+
hint: t("skills.section.localHint"),
|
|
2091
|
+
items: local,
|
|
2092
|
+
cardFn: localCard,
|
|
2093
|
+
emptyKey: "skills.local.empty",
|
|
2094
|
+
}, {
|
|
2095
|
+
id: "cloud",
|
|
2096
|
+
label: t("skills.section.cloud"),
|
|
2097
|
+
hint: t("skills.section.cloudHint"),
|
|
2098
|
+
items: cloud,
|
|
2099
|
+
cardFn: cloudCard,
|
|
2100
|
+
emptyKey: locked ? "skills.cloud.locked" : "skills.cloud.empty",
|
|
2101
|
+
}];
|
|
2102
|
+
sectionTabs(sections, sectionState).forEach((node) => container.appendChild(node));
|
|
1920
2103
|
}
|
|
1921
2104
|
|
|
1922
2105
|
return {
|
|
@@ -2002,13 +2185,33 @@
|
|
|
2002
2185
|
return item;
|
|
2003
2186
|
}
|
|
2004
2187
|
|
|
2188
|
+
let creatorNavItem = null;
|
|
2189
|
+
let brandStatusReady = false;
|
|
2190
|
+
|
|
2191
|
+
function syncCreatorNavVisibility() {
|
|
2192
|
+
if (!creatorNavItem) return;
|
|
2193
|
+
const brandNonAdmin = typeof Brand !== "undefined" && Brand.branded && !Brand.userLicensed;
|
|
2194
|
+
creatorNavItem.hidden = !brandStatusReady || brandNonAdmin;
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
if (typeof Brand !== "undefined") {
|
|
2198
|
+
Brand.on("brand:status", function () {
|
|
2199
|
+
brandStatusReady = true;
|
|
2200
|
+
syncCreatorNavVisibility();
|
|
2201
|
+
});
|
|
2202
|
+
} else {
|
|
2203
|
+
brandStatusReady = true;
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2005
2206
|
Clacky.ext.ui.registerWorkspace(WS_ID, {
|
|
2006
2207
|
title: t("ws.title"),
|
|
2007
2208
|
render(container) { renderWorkspace(container); },
|
|
2008
2209
|
});
|
|
2009
2210
|
|
|
2010
2211
|
Clacky.ext.ui.mount("sidebar.nav.bottom", function () {
|
|
2011
|
-
|
|
2212
|
+
creatorNavItem = navRow("nav.entry", function () { Clacky.ext.ui.openWorkspace(WS_ID); });
|
|
2213
|
+
syncCreatorNavVisibility();
|
|
2214
|
+
return creatorNavItem;
|
|
2012
2215
|
}, { workspace: WS_ID });
|
|
2013
2216
|
|
|
2014
2217
|
// Extension-developer session tools: debug + publish, as aside tabs. These
|
|
@@ -2041,7 +2244,7 @@
|
|
|
2041
2244
|
.studio-detail-desc { margin: 4px 0 0; font-size: 12px; color: var(--color-text-secondary); line-height: 1.5; }
|
|
2042
2245
|
.studio-units { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
2043
2246
|
.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; }
|
|
2044
|
-
.studio-actions { display: flex; align-items: center; gap: 8px; margin: 12px 0 0; }
|
|
2247
|
+
.studio-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 12px 0 0; }
|
|
2045
2248
|
.studio-skill-promo .studio-actions { margin: 12px 0 0; }
|
|
2046
2249
|
.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; }
|
|
2047
2250
|
.studio-btn:hover { background: var(--color-bg-hover); color: var(--color-text-primary); }
|
|
@@ -2083,15 +2286,14 @@
|
|
|
2083
2286
|
.studio-page-head-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
|
2084
2287
|
.studio-page-title { margin: 0; font-size: 22px; font-weight: 600; color: var(--color-text-primary); }
|
|
2085
2288
|
.studio-btn-sm { padding: 5px 12px; font-size: 12px; }
|
|
2289
|
+
.studio-seg-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
|
|
2290
|
+
.studio-seg-hint { font-size: 11px; color: var(--color-text-muted); }
|
|
2086
2291
|
.studio-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--color-border-primary); margin-bottom: 24px; }
|
|
2087
2292
|
.studio-tab { padding: 8px 16px; border: none; background: transparent; color: var(--color-text-tertiary); cursor: pointer; font-size: 14px; font-weight: 500; border-bottom: 2px solid transparent; margin-bottom: -1px; }
|
|
2088
2293
|
.studio-tab:hover { color: var(--color-text-primary); }
|
|
2089
2294
|
.studio-tab-active { color: var(--color-text-primary); border-bottom-color: var(--color-accent-primary); }
|
|
2090
2295
|
.studio-ext-block { margin-bottom: 20px; }
|
|
2091
2296
|
.studio-block-title { font-size: 13px; font-weight: 600; color: var(--color-text-secondary); padding: 8px 0 0; }
|
|
2092
|
-
.studio-skill-section { margin-bottom: 28px; }
|
|
2093
|
-
.studio-skill-section-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 12px; }
|
|
2094
|
-
.studio-skill-hint { font-size: 11px; color: var(--color-text-muted); }
|
|
2095
2297
|
.studio-skill-card { border: 1px solid var(--color-border-primary); border-radius: var(--radius-md, 8px); padding: 12px; margin: 0 0 10px; background: var(--color-bg-secondary); }
|
|
2096
2298
|
.studio-skill-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
2097
2299
|
.studio-skill-name { font-size: 14px; font-weight: 600; color: var(--color-text-primary); }
|
|
@@ -2099,6 +2301,7 @@
|
|
|
2099
2301
|
.studio-skill-badge-published { background: var(--color-success-bg, var(--color-bg-hover)); color: var(--color-success); }
|
|
2100
2302
|
.studio-skill-badge-local { background: var(--color-bg-hover); color: var(--color-text-tertiary); }
|
|
2101
2303
|
.studio-skill-badge-changed { background: var(--color-warning-bg, var(--color-bg-hover)); color: var(--color-warning, var(--color-text-secondary)); }
|
|
2304
|
+
.studio-skill-badge-unlisted { background: var(--color-warning-bg, var(--color-bg-hover)); color: var(--color-warning, var(--color-text-secondary)); }
|
|
2102
2305
|
.studio-skill-badge-shadow { background: var(--color-bg-hover); color: var(--color-text-secondary); }
|
|
2103
2306
|
.studio-skill-desc { margin: 6px 0 6px; font-size: 12px; color: var(--color-text-secondary); line-height: 1.5; }
|
|
2104
2307
|
.studio-skill-meta { display: flex; gap: 12px; font-size: 11px; color: var(--color-text-muted); margin-top: 6px; }
|
|
@@ -2150,6 +2353,12 @@
|
|
|
2150
2353
|
.studio-readme-preview pre code, .extension-readme-body pre code { background: none; padding: 0; }
|
|
2151
2354
|
.studio-link-btn { background: none; border: none; padding: 0; font-size: 12px; color: var(--color-accent-primary); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
|
|
2152
2355
|
.studio-link-btn:hover { color: var(--color-accent-hover); }
|
|
2356
|
+
.studio-actions-overflow { display: none; align-items: center; justify-content: center; align-self: stretch; box-sizing: border-box; padding: 7px 12px; }
|
|
2357
|
+
.studio-overflow-menu .session-actions-menu-item.is-disabled { opacity: 0.5; cursor: default; }
|
|
2358
|
+
@media (max-width: 768px) {
|
|
2359
|
+
.studio-actions > .studio-btn-collapsible { display: none; }
|
|
2360
|
+
.studio-actions > .studio-actions-overflow { display: inline-flex; }
|
|
2361
|
+
}
|
|
2153
2362
|
`;
|
|
2154
2363
|
document.head.appendChild(style);
|
|
2155
2364
|
})();
|