Collapsible shortcut/transition rows, optional replacement, name fallback

- Shortcuts and Transitions: saved entries collapse to a summary row (name +
  on/off badge); click to expand, Collapse button to close without saving
- Transition summary also shows trigger phrase in italic next to the name
- Shortcuts: replacement is no longer required; blank/missing defaults to [""]
  which removes the matched phrase (same as explicit empty replacement)
- Shortcuts: name falls back to first activation word when left blank
- New summary row CSS: badge-on/badge-off, label, trigger styles

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-05-22 18:16:59 +03:00
co-authored by Claude Sonnet 4.6
parent 889e4dc2c8
commit acdc4c41d6
2 changed files with 82 additions and 5 deletions
+3 -3
View File
@@ -304,10 +304,10 @@ def normalize_shortcut_item(item, fallback_name=""):
return None
name = normalize_shortcut_name(item.get("name"), fallback_name)
triggers = shortcut_list(item.get("triggers", item.get("activation_words", item.get("activation", []))))
replacements = _shortcut_replacements(item.get("replacements", item.get("replacement", [])))
replacements = _shortcut_replacements(item.get("replacements", item.get("replacement", []))) or [""]
if not name:
name = triggers[0] if triggers else ""
if not name or not triggers or not replacements:
if not name or not triggers:
return None
created = str(item.get("created_at") or now_iso())
return {
@@ -376,7 +376,7 @@ def shortcut_items(data=None):
def save_shortcut_item(payload):
item = normalize_shortcut_item(payload)
if item is None:
raise ValueError("Shortcut name, activation phrase, and replacement phrase are required.")
raise ValueError("Shortcut activation phrase and replacement phrase are required.")
data = load_shortcut_db()
shortcuts = data.setdefault("shortcuts", {})
key = shortcut_key(item["name"])
+79 -2
View File
@@ -659,7 +659,23 @@ function openPanel(node) {
}
drafts.forEach((item, index) => {
const isSaved = !!item.key;
if (!("_expanded" in item)) item._expanded = !isSaved;
const rowEl = el("div", "funpack-studio-shortcut-row");
if (!item._expanded) {
const summary = el("div", "funpack-studio-shortcut-summary");
const label = el("span", "funpack-studio-shortcut-label", item.name || "(unnamed)");
const badge = el("span", item.enabled !== false ? "funpack-studio-badge-on" : "funpack-studio-badge-off",
item.enabled !== false ? "on" : "off");
summary.append(label, badge);
summary.addEventListener("click", () => { item._expanded = true; renderTab("Shortcuts"); });
rowEl.append(summary);
list.append(rowEl);
return;
}
const top = el("div", "funpack-studio-shortcut-top");
const nameInput = textInput(item.name || "", "Shortcut name");
nameInput.addEventListener("input", () => { item.name = nameInput.value; });
@@ -693,6 +709,8 @@ function openPanel(node) {
renderTab("Shortcuts");
} catch (e) { showError(root, e.message); }
});
const collapseBtn = btn("Collapse");
collapseBtn.addEventListener("click", () => { item._expanded = false; renderTab("Shortcuts"); });
const delBtn = btn("Delete", "danger");
delBtn.addEventListener("click", async () => {
try {
@@ -705,7 +723,7 @@ function openPanel(node) {
renderTab("Shortcuts");
} catch (e) { showError(root, e.message); }
});
actions.append(saveBtn, delBtn);
actions.append(saveBtn, collapseBtn, delBtn);
rowEl.append(top, triggerArea, replacementArea, actions);
list.append(rowEl);
});
@@ -764,7 +782,24 @@ function openPanel(node) {
}
drafts.forEach((item, index) => {
const isSaved = !!item.key;
if (!("_expanded" in item)) item._expanded = !isSaved;
const rowEl = el("div", "funpack-studio-shortcut-row");
if (!item._expanded) {
const summary = el("div", "funpack-studio-shortcut-summary");
const label = el("span", "funpack-studio-shortcut-label", item.name || item.trigger || "(unnamed)");
const trigger = el("span", "funpack-studio-shortcut-trigger", item.trigger || "");
const badge = el("span", item.enabled !== false ? "funpack-studio-badge-on" : "funpack-studio-badge-off",
item.enabled !== false ? "on" : "off");
summary.append(label, trigger, badge);
summary.addEventListener("click", () => { item._expanded = true; renderTab("Transitions"); });
rowEl.append(summary);
list.append(rowEl);
return;
}
const top = el("div", "funpack-studio-shortcut-top");
const nameInput = textInput(item.name || "", "Label (display only)");
nameInput.addEventListener("input", () => { item.name = nameInput.value; });
@@ -790,6 +825,8 @@ function openPanel(node) {
renderTab("Transitions");
} catch (e) { showError(root, e.message); }
});
const collapseBtn = btn("Collapse");
collapseBtn.addEventListener("click", () => { item._expanded = false; renderTab("Transitions"); });
const delBtn = btn("Delete", "danger");
delBtn.addEventListener("click", async () => {
try {
@@ -802,7 +839,7 @@ function openPanel(node) {
renderTab("Transitions");
} catch (e) { showError(root, e.message); }
});
actions.append(saveBtn, delBtn);
actions.append(saveBtn, collapseBtn, delBtn);
rowEl.append(top, triggerInput, actions);
list.append(rowEl);
});
@@ -1334,6 +1371,46 @@ function injectStyles() {
justify-content: flex-end;
flex-wrap: wrap;
}
.funpack-studio-shortcut-summary {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 2px 0;
}
.funpack-studio-shortcut-summary:hover .funpack-studio-shortcut-label {
text-decoration: underline;
}
.funpack-studio-shortcut-label {
font-weight: 500;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.funpack-studio-shortcut-trigger {
font-size: 0.82em;
color: rgba(255,255,255,0.45);
font-style: italic;
white-space: nowrap;
}
.funpack-studio-badge-on {
font-size: 0.75em;
padding: 1px 6px;
border-radius: 10px;
background: rgba(80,180,100,0.25);
color: #7ddb8f;
white-space: nowrap;
}
.funpack-studio-badge-off {
font-size: 0.75em;
padding: 1px 6px;
border-radius: 10px;
background: rgba(255,255,255,0.07);
color: rgba(255,255,255,0.35);
white-space: nowrap;
}
.lora-name, .lora-type { min-height: 28px; padding: 4px 6px;
border: 1px solid rgba(180,190,200,0.28); border-radius: 5px;
background: #17191d; color: #f2f2f2; outline: none; width: 100%; }