Files
ST-Bionic-Memory-Ecology/ui/notice.js
Youzini-afk cc527f63d7 fix(notice): compact layout -- hide message/actions with display:none to prevent flex width pollution
The hidden __message (max-height:0, opacity:0) still had white-space:
pre-wrap which contributed to flex intrinsic width calculation in compact
layout, causing the __content flex container to miscalculate width and
truncate the title to 'ST...'.

Also add flex:0 1 auto + min-width:0 to compact __title so it properly
shrinks with ellipsis instead of overflow when space is limited.
2026-04-29 17:34:11 +08:00

598 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const STYLE_ID = "st-bme-notice-style";
const HOST_ID = "st-bme-notice-host";
function resolveNoticeDocument() {
const runtime = globalThis;
const chatDocument = runtime?.SillyTavern?.Chat?.document;
if (chatDocument && typeof chatDocument.createElement === "function") {
return chatDocument;
}
try {
return (window.parent && window.parent !== window ? window.parent : window).document;
} catch {
return document;
}
}
function ensureStyle(doc) {
if (doc.getElementById(STYLE_ID)) return;
const style = doc.createElement("style");
style.id = STYLE_ID;
style.textContent = `
#${HOST_ID} {
position: fixed;
top: 14px;
right: 14px;
z-index: 12020;
width: min(400px, calc(100vw - 28px));
display: flex;
flex-direction: column;
gap: 10px;
pointer-events: none;
}
.st-bme-notice {
--st-bme-accent: #73b8ff;
pointer-events: auto;
position: relative;
display: grid;
grid-template-columns: auto 1fr auto;
gap: 12px;
align-items: start;
padding: 12px 12px 12px 10px;
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.16);
background:
radial-gradient(circle at 10% -10%, rgba(115, 184, 255, 0.2), transparent 52%),
linear-gradient(145deg, rgba(27, 37, 54, 0.95), rgba(12, 18, 29, 0.93));
box-shadow:
0 14px 34px rgba(4, 10, 17, 0.46),
inset 0 0 0 1px rgba(255, 255, 255, 0.04);
color: #edf3fb;
overflow: hidden;
transform: translateY(-8px) scale(0.985);
opacity: 0;
animation: stBmeNoticeIn 190ms ease forwards;
font-family: "Noto Sans SC", "PingFang SC", "Microsoft YaHei UI", sans-serif;
backdrop-filter: blur(10px) saturate(125%);
-webkit-backdrop-filter: blur(10px) saturate(125%);
transition:
grid-template-columns 260ms cubic-bezier(0.22, 1, 0.36, 1),
padding 260ms cubic-bezier(0.22, 1, 0.36, 1),
border-radius 260ms cubic-bezier(0.22, 1, 0.36, 1),
width 260ms cubic-bezier(0.22, 1, 0.36, 1);
}
.st-bme-notice[data-layout="compact"] {
display: flex;
align-items: center;
gap: 10px;
max-width: min(400px, calc(100vw - 28px));
align-self: flex-end;
padding: 10px;
}
.st-bme-notice__content {
min-width: 0;
flex: 1 1 0%;
transition: max-width 280ms cubic-bezier(0.22, 1, 0.36, 1),
opacity 220ms ease;
}
.st-bme-notice[data-layout="normal"] .st-bme-notice__content {
overflow: hidden;
}
.st-bme-notice[data-layout="compact"] .st-bme-notice__content {
display: flex;
align-items: center;
min-width: 0;
overflow: hidden;
}
.st-bme-notice__title {
margin: 0;
font-size: 17px;
line-height: 1.18;
font-weight: 800;
letter-spacing: 0.01em;
color: #f0f6ff;
transition: font-size 260ms ease;
}
.st-bme-notice[data-layout="compact"] .st-bme-notice__title {
font-size: 16px;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 0 1 auto;
min-width: 0;
}
.st-bme-notice__message {
margin: 4px 0 0;
font-size: 14px;
line-height: 1.38;
color: rgba(240, 246, 255, 0.86);
white-space: pre-wrap;
word-break: break-word;
overflow: hidden;
transition: max-height 280ms cubic-bezier(0.22, 1, 0.36, 1),
opacity 220ms ease,
margin 260ms ease;
}
.st-bme-notice[data-layout="normal"] .st-bme-notice__message {
max-height: 200px;
opacity: 1;
}
.st-bme-notice[data-layout="compact"] .st-bme-notice__message {
max-height: 0;
opacity: 0;
margin-top: 0;
display: none;
}
.st-bme-notice::after {
content: "";
position: absolute;
inset: 0;
border-left: 3px solid var(--st-bme-accent);
border-radius: 14px;
pointer-events: none;
opacity: 0.9;
}
.st-bme-notice--out {
animation: stBmeNoticeOut 160ms ease forwards;
}
.st-bme-notice__icon {
width: 30px;
height: 30px;
border-radius: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 15px;
font-weight: 800;
color: #f4f8ff;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.14);
box-shadow: inset 0 1px 1px rgba(255, 255, 255, 0.16);
flex-shrink: 0;
}
.st-bme-notice[data-busy="true"] .st-bme-notice__icon {
animation: stBmeNoticeBusy 900ms linear infinite;
}
.st-bme-notice__message--marquee {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: "Cascadia Code", "Fira Code", "JetBrains Mono", monospace;
font-size: 12.5px;
color: rgba(240, 246, 255, 0.72);
mask-image: linear-gradient(90deg, transparent 0%, black 6%, black 88%, transparent 100%);
-webkit-mask-image: linear-gradient(90deg, transparent 0%, black 6%, black 88%, transparent 100%);
}
.st-bme-notice__action {
min-height: 30px;
padding: 0 12px;
border-radius: 999px;
border: 1px solid rgba(255, 255, 255, 0.16);
background: rgba(255, 255, 255, 0.08);
color: #eef4ff;
font-size: 13px;
font-weight: 700;
cursor: pointer;
transition: background 140ms ease, border-color 140ms ease, transform 140ms ease;
}
.st-bme-notice__action:hover,
.st-bme-notice__action:focus-visible {
background: rgba(255, 255, 255, 0.16);
border-color: rgba(255, 255, 255, 0.24);
transform: translateY(-1px);
outline: none;
}
.st-bme-notice__action[data-kind="danger"] {
background: rgba(245, 123, 143, 0.16);
border-color: rgba(245, 123, 143, 0.42);
color: #ffd9df;
}
.st-bme-notice__close {
width: 22px;
height: 22px;
border: none;
border-radius: 7px;
background: rgba(255, 255, 255, 0.08);
color: #d7e0ec;
font-size: 15px;
line-height: 1;
cursor: pointer;
transition: background 140ms ease;
flex-shrink: 0;
}
.st-bme-notice__close:hover,
.st-bme-notice__close:focus-visible {
background: rgba(255, 255, 255, 0.2);
outline: none;
}
.st-bme-notice__toggle-hint {
position: absolute;
bottom: 6px;
right: 32px;
font-size: 10px;
color: rgba(240, 246, 255, 0.35);
pointer-events: none;
transition: opacity 180ms ease;
opacity: 0;
}
.st-bme-notice:hover .st-bme-notice__toggle-hint {
opacity: 1;
}
.st-bme-notice[data-layout="normal"] .st-bme-notice__toggle-hint::after {
content: "▸ 简洁";
}
.st-bme-notice[data-layout="compact"] .st-bme-notice__toggle-hint::after {
content: "▸ 详细";
}
.st-bme-notice--switching {
pointer-events: none;
}
.st-bme-notice--switching .st-bme-notice__content {
opacity: 0;
}
.st-bme-notice__actions {
display: flex;
gap: 8px;
margin-top: 10px;
overflow: hidden;
transition: max-height 260ms cubic-bezier(0.22, 1, 0.36, 1),
opacity 220ms ease,
margin 260ms ease;
}
.st-bme-notice[data-layout="compact"] .st-bme-notice__actions {
max-height: 0;
opacity: 0;
margin-top: 0;
overflow: hidden;
display: none;
}
.st-bme-notice[data-layout="normal"] .st-bme-notice__actions {
max-height: 60px;
opacity: 1;
}
.st-bme-notice[data-layout="compact"] .st-bme-notice__progress {
display: none;
}
.st-bme-notice__progress {
position: absolute;
left: 0;
bottom: 0;
height: 2px;
width: 100%;
background: linear-gradient(90deg, var(--st-bme-accent), rgba(255, 255, 255, 0.24));
transform-origin: left center;
animation: stBmeNoticeProgress linear forwards;
}
.st-bme-notice[data-level="success"] {
--st-bme-accent: #65d39c;
}
.st-bme-notice[data-level="error"] {
--st-bme-accent: #f57b8f;
}
.st-bme-notice[data-level="warning"] {
--st-bme-accent: #eab96f;
}
@keyframes stBmeNoticeIn {
to {
transform: translateY(0) scale(1);
opacity: 1;
}
}
@keyframes stBmeNoticeOut {
to {
transform: translateY(-6px) scale(0.98);
opacity: 0;
}
}
@keyframes stBmeNoticeProgress {
from {
transform: scaleX(1);
}
to {
transform: scaleX(0);
}
}
@keyframes stBmeNoticeBusy {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (max-width: 900px) {
#${HOST_ID} {
top: 8px;
right: 8px;
width: calc(100vw - 16px);
}
}
@media (prefers-reduced-motion: reduce) {
.st-bme-notice,
.st-bme-notice--out,
.st-bme-notice__progress {
animation-duration: 1ms !important;
}
}
`;
(doc.head || doc.documentElement).appendChild(style);
}
function ensureHost(doc) {
let host = doc.getElementById(HOST_ID);
if (host) return host;
host = doc.createElement("div");
host.id = HOST_ID;
host.setAttribute("aria-live", "polite");
host.setAttribute("aria-atomic", "false");
(doc.body || doc.documentElement).appendChild(host);
return host;
}
function getIcon(level) {
switch (level) {
case "success":
return "✓";
case "error":
return "!";
case "warning":
return "△";
default:
return "i";
}
}
function applyNoticeState(item, input, progress) {
const level = input.level || "info";
const displayMode = input.displayMode === "compact" ? "compact" : "normal";
const isCompact = displayMode === "compact";
item.dataset.level = level;
item.dataset.busy = input.busy ? "true" : "false";
item.dataset.layout = displayMode;
const icon = item.querySelector(".st-bme-notice__icon");
if (icon) {
icon.textContent = input.busy ? "◌" : getIcon(level);
}
const title = item.querySelector(".st-bme-notice__title");
if (title) {
title.textContent = input.title || "ST-BME";
}
const message = item.querySelector(".st-bme-notice__message");
if (message) {
message.textContent = input.message || "";
message.hidden = !String(input.message || "").trim();
if (input.marquee) {
message.classList.add("st-bme-notice__message--marquee");
} else {
message.classList.remove("st-bme-notice__message--marquee");
}
}
const actionWrap = item.querySelector(".st-bme-notice__actions");
const actionButton = item.querySelector(".st-bme-notice__action");
if (actionWrap && actionButton) {
if (input.action?.label) {
actionButton.style.display = "";
actionButton.textContent = input.action.label;
actionButton.dataset.kind = input.action.kind || "neutral";
} else {
actionButton.style.display = "none";
actionButton.textContent = "";
actionButton.dataset.kind = "neutral";
}
}
if (input.persist || isCompact) {
progress.style.display = "none";
progress.style.animationDuration = "";
} else {
const duration = Math.max(1400, input.duration_ms || 3200);
progress.style.display = "";
progress.style.animationDuration = `${duration}ms`;
}
}
export function showManagedBmeNotice(input) {
const doc = resolveNoticeDocument();
ensureStyle(doc);
const host = ensureHost(doc);
const item = doc.createElement("article");
item.className = "st-bme-notice";
const icon = doc.createElement("span");
icon.className = "st-bme-notice__icon";
const content = doc.createElement("div");
content.className = "st-bme-notice__content";
const title = doc.createElement("h4");
title.className = "st-bme-notice__title";
const message = doc.createElement("p");
message.className = "st-bme-notice__message";
const actions = doc.createElement("div");
actions.className = "st-bme-notice__actions";
const actionButton = doc.createElement("button");
actionButton.className = "st-bme-notice__action";
actionButton.type = "button";
actionButton.style.display = "none";
const closeButton = doc.createElement("button");
closeButton.className = "st-bme-notice__close";
closeButton.type = "button";
closeButton.setAttribute("aria-label", "关闭提示");
closeButton.textContent = "×";
const progress = doc.createElement("div");
progress.className = "st-bme-notice__progress";
const toggleHint = doc.createElement("span");
toggleHint.className = "st-bme-notice__toggle-hint";
content.appendChild(title);
content.appendChild(message);
actions.appendChild(actionButton);
content.appendChild(actions);
item.appendChild(icon);
item.appendChild(content);
item.appendChild(closeButton);
item.appendChild(toggleHint);
item.appendChild(progress);
let currentInput = input || {};
let closed = false;
let closeTimer = null;
let switching = false;
const clearCloseTimer = () => {
if (!closeTimer) return;
clearTimeout(closeTimer);
closeTimer = null;
};
const close = () => {
if (closed) return;
clearCloseTimer();
closed = true;
item.classList.add("st-bme-notice--out");
setTimeout(() => {
item.remove();
if (!host.childElementCount) {
host.remove();
}
}, 170);
};
const scheduleAutoClose = (nextInput) => {
clearCloseTimer();
if (nextInput.persist) return;
const duration = Math.max(1400, nextInput.duration_ms || 3200);
closeTimer = setTimeout(close, duration);
};
const update = (nextInput) => {
if (closed) return;
currentInput = nextInput || {};
applyNoticeState(item, currentInput, progress);
scheduleAutoClose(currentInput);
};
applyNoticeState(item, currentInput, progress);
scheduleAutoClose(currentInput);
actionButton.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
currentInput.action?.onClick?.();
});
closeButton.addEventListener("click", (event) => {
event.preventDefault();
event.stopPropagation();
close();
});
content.addEventListener("click", (event) => {
if (closed || switching) return;
if (event.target === actionButton || event.target === closeButton) return;
if (actionButton.contains(event.target) || closeButton.contains(event.target)) return;
switching = true;
item.classList.add("st-bme-notice--switching");
const currentLayout = item.dataset.layout || "normal";
const nextLayout = currentLayout === "compact" ? "normal" : "compact";
setTimeout(() => {
item.dataset.layout = nextLayout;
currentInput.displayMode = nextLayout;
if (nextLayout === "normal") {
const msgEl = item.querySelector(".st-bme-notice__message");
if (msgEl) msgEl.hidden = !String(currentInput.message || "").trim();
}
if (nextLayout === "normal" && !currentInput.persist) {
clearCloseTimer();
const duration = Math.max(1400, currentInput.duration_ms || 3200);
closeTimer = setTimeout(close, duration);
const progressEl = item.querySelector(".st-bme-notice__progress");
if (progressEl) {
progressEl.style.display = "";
progressEl.style.animationDuration = `${duration}ms`;
progressEl.style.animation = "none";
void progressEl.offsetHeight;
progressEl.style.animation = "";
}
}
setTimeout(() => {
item.classList.remove("st-bme-notice--switching");
switching = false;
}, 60);
}, 180);
});
host.appendChild(item);
return {
update,
dismiss: close,
isClosed: () => closed,
};
}
export function showBmeNotice(input) {
return showManagedBmeNotice(input);
}