feat(ui): add refresh button for showhtml cards
This commit is contained in:
parent
591a5e64cc
commit
3cd8bdabef
@ -380,6 +380,45 @@ function escapeHtml(input: string) {
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function bindShowHtmlRefreshControl(wrapper: HTMLElement, onRefresh: () => void) {
|
||||
let toolbar = wrapper.querySelector(':scope > .chat-inline-card__toolbar') as HTMLElement | null;
|
||||
if (!toolbar) {
|
||||
toolbar = document.createElement('div');
|
||||
toolbar.className = 'chat-inline-card__toolbar';
|
||||
wrapper.appendChild(toolbar);
|
||||
}
|
||||
let refreshBtn = toolbar.querySelector(
|
||||
':scope > .chat-inline-card__refresh-btn'
|
||||
) as HTMLButtonElement | null;
|
||||
if (!refreshBtn) {
|
||||
refreshBtn = document.createElement('button');
|
||||
refreshBtn.type = 'button';
|
||||
refreshBtn.className = 'chat-inline-card__refresh-btn';
|
||||
refreshBtn.title = '刷新卡片';
|
||||
refreshBtn.setAttribute('aria-label', '刷新卡片');
|
||||
refreshBtn.textContent = '↻';
|
||||
toolbar.appendChild(refreshBtn);
|
||||
}
|
||||
refreshBtn.onclick = (event) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
markShowTagDrawingActive(1200);
|
||||
refreshBtn!.classList.remove('is-refreshing');
|
||||
// 触发重放动画
|
||||
void refreshBtn!.offsetWidth;
|
||||
refreshBtn!.classList.add('is-refreshing');
|
||||
try {
|
||||
onRefresh();
|
||||
} catch (error) {
|
||||
debugShowHtmlLog('refresh:error', {
|
||||
message: error instanceof Error ? error.message : String(error || '')
|
||||
});
|
||||
} finally {
|
||||
window.setTimeout(() => refreshBtn?.classList.remove('is-refreshing'), 720);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function renderShowImages(root: ParentNode | null = document) {
|
||||
if (!root) return;
|
||||
const debugMode = getShowImageDebugMode();
|
||||
@ -648,6 +687,24 @@ function renderShowImages(root: ParentNode | null = document) {
|
||||
};
|
||||
showHtmlJsIframeRenderByPath.set(pathKey, persistent);
|
||||
}
|
||||
bindShowHtmlRefreshControl(persistent.wrapper, () => {
|
||||
// 强制重新挂载同一份 srcdoc,触发 iframe 内页面重载
|
||||
const keep = persistent?.srcdoc || srcdoc;
|
||||
persistent.iframe.srcdoc =
|
||||
'<!doctype html><html><head><meta charset="utf-8"></head><body></body></html>';
|
||||
requestAnimationFrame(() => {
|
||||
if (persistent?.iframe) {
|
||||
persistent.iframe.srcdoc = keep;
|
||||
}
|
||||
});
|
||||
debugShowHtmlLog('refresh:iframe', {
|
||||
pathKey,
|
||||
ratioKey: persistent?.ratioKey || ratioKey,
|
||||
widthPx: persistent?.widthPx || widthPx,
|
||||
heightPx: persistent?.heightPx || heightPx,
|
||||
srcdocLength: keep.length
|
||||
});
|
||||
});
|
||||
persistent.wrapper.style.width = `${widthPx}px`;
|
||||
persistent.iframe.style.height = `${heightPx}px`;
|
||||
const needsUpdate =
|
||||
@ -784,6 +841,7 @@ function renderShowImages(root: ParentNode | null = document) {
|
||||
wrapper.appendChild(host);
|
||||
persistent = {
|
||||
encoded: '',
|
||||
safeHtml: '',
|
||||
ratioKey: '1:1',
|
||||
widthPx: 0,
|
||||
heightPx: 0,
|
||||
@ -793,6 +851,22 @@ function renderShowImages(root: ParentNode | null = document) {
|
||||
};
|
||||
showHtmlPersistentRenderByPath.set(pathKey, persistent);
|
||||
}
|
||||
bindShowHtmlRefreshControl(persistent.wrapper, () => {
|
||||
const keep = persistent?.safeHtml || '';
|
||||
persistent.root.innerHTML = '';
|
||||
requestAnimationFrame(() => {
|
||||
if (persistent?.root) {
|
||||
persistent.root.innerHTML = keep;
|
||||
}
|
||||
});
|
||||
debugShowHtmlLog('refresh:shadow-root', {
|
||||
pathKey,
|
||||
ratioKey: persistent?.ratioKey || ratioKey,
|
||||
widthPx: persistent?.widthPx || widthPx,
|
||||
heightPx: persistent?.heightPx || heightPx,
|
||||
safeLength: keep.length
|
||||
});
|
||||
});
|
||||
|
||||
persistent.wrapper.style.width = `${widthPx}px`;
|
||||
persistent.host.style.height = `${heightPx}px`;
|
||||
@ -804,6 +878,7 @@ function renderShowImages(root: ParentNode | null = document) {
|
||||
if (needsUpdate) {
|
||||
persistent.root.innerHTML = safeHtml;
|
||||
persistent.encoded = effectiveEncoded;
|
||||
persistent.safeHtml = safeHtml;
|
||||
persistent.widthPx = widthPx;
|
||||
persistent.heightPx = heightPx;
|
||||
persistent.ratioKey = ratioKey;
|
||||
@ -888,6 +963,7 @@ const showHtmlPersistentRenderByPath = new Map<
|
||||
string,
|
||||
{
|
||||
encoded: string,
|
||||
safeHtml: string,
|
||||
ratioKey: string,
|
||||
widthPx: number,
|
||||
heightPx: number,
|
||||
|
||||
@ -965,6 +965,54 @@ body[data-theme='dark'] .more-icon {
|
||||
|
||||
.chat-inline-card--html {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chat-inline-card__toolbar {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
z-index: 4;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.chat-inline-card__refresh-btn {
|
||||
pointer-events: auto;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--claude-border);
|
||||
background: color-mix(in srgb, var(--claude-card) 78%, transparent);
|
||||
color: var(--claude-text-secondary);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
|
||||
}
|
||||
|
||||
.chat-inline-card__refresh-btn:hover {
|
||||
color: var(--claude-text);
|
||||
border-color: var(--claude-accent);
|
||||
background: color-mix(in srgb, var(--claude-highlight) 72%, transparent);
|
||||
}
|
||||
|
||||
.chat-inline-card__refresh-btn.is-refreshing {
|
||||
animation: chat-inline-refresh-spin 0.6s linear 1;
|
||||
}
|
||||
|
||||
@keyframes chat-inline-refresh-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.chat-inline-html__host--pending {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user