Improve WeChat MP replies and ship MindSpace/H5 production updates.
Add WeChat service account routing with sync acks, connectivity tests, and context isolation; document deploy runbooks; and bundle related MindSpace, voice, Plaza, and server gateway changes for production rollout. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -24,9 +24,57 @@ th{background:#faf7ef}
|
||||
.docx-preview p{margin:0 0 12px;text-indent:2em}
|
||||
.docx-preview .meta{color:#6b7280;font-size:13px;margin-bottom:20px;text-indent:0}
|
||||
.pdf-frame,.image-frame{display:block;width:100%;min-height:calc(100vh - 48px);border:0;border-radius:12px;background:#fff}
|
||||
.image-frame{object-fit:contain;max-height:calc(100vh - 48px);width:auto;max-width:100%;margin:0 auto}
|
||||
.image-frame{object-fit:contain;max-height:calc(100vh - 48px);width:auto;max-width:100%;margin:0 auto;cursor:zoom-in}
|
||||
.image-viewer{padding:0}
|
||||
.image-viewer h1,.image-viewer .meta{display:none}
|
||||
.image-viewer .image-frame{min-height:100vh;max-height:100vh;margin:0;border-radius:0;background:#0b100e}
|
||||
.image-lightbox[hidden]{display:none}
|
||||
.image-lightbox{position:fixed;inset:0;z-index:9999;display:grid;place-items:center;padding:max(16px,env(safe-area-inset-top)) max(16px,env(safe-area-inset-right)) max(16px,env(safe-area-inset-bottom)) max(16px,env(safe-area-inset-left));background:rgba(7,12,10,.9);cursor:zoom-out}
|
||||
.image-lightbox img{display:block;max-width:min(100%,1600px);max-height:calc(100vh - 32px);object-fit:contain;border-radius:8px;box-shadow:0 24px 80px rgba(0,0,0,.35);cursor:default}
|
||||
.image-lightbox-close{position:fixed;top:max(16px,env(safe-area-inset-top));right:max(16px,env(safe-area-inset-right));z-index:10000;display:grid;place-items:center;width:40px;height:40px;padding:0;border:0;border-radius:999px;color:#fffaf0;background:rgba(24,33,29,.72);box-shadow:0 8px 24px rgba(0,0,0,.28);cursor:pointer}
|
||||
.image-lightbox-close svg{display:block;width:18px;height:18px;stroke:currentColor;stroke-width:2;fill:none}
|
||||
.image-lightbox-close:hover{background:rgba(24,33,29,.92)}
|
||||
`;
|
||||
|
||||
const IMAGE_LIGHTBOX_CLOSE_ICON =
|
||||
'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>';
|
||||
|
||||
const IMAGE_LIGHTBOX_SCRIPT = `<script>
|
||||
(function () {
|
||||
var trigger = document.querySelector('[data-image-lightbox-trigger]');
|
||||
var lightbox = document.querySelector('.image-lightbox');
|
||||
if (!trigger || !lightbox) return;
|
||||
var closeBtn = lightbox.querySelector('.image-lightbox-close');
|
||||
|
||||
function openLightbox() {
|
||||
lightbox.hidden = false;
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeLightbox() {
|
||||
lightbox.hidden = true;
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
trigger.addEventListener('click', function () {
|
||||
openLightbox();
|
||||
});
|
||||
closeBtn.addEventListener('click', function (event) {
|
||||
event.stopPropagation();
|
||||
closeLightbox();
|
||||
});
|
||||
lightbox.addEventListener('click', function (event) {
|
||||
if (event.target === lightbox) closeLightbox();
|
||||
});
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape' && !lightbox.hidden) {
|
||||
event.preventDefault();
|
||||
closeLightbox();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>`;
|
||||
|
||||
function escapeHtml(text) {
|
||||
return String(text ?? '')
|
||||
.replace(/&/g, '&')
|
||||
@@ -35,7 +83,11 @@ function escapeHtml(text) {
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function previewDocument(title, bodyHtml, { downloadUrl = null, extraHead = '' } = {}) {
|
||||
function previewDocument(
|
||||
title,
|
||||
bodyHtml,
|
||||
{ downloadUrl = null, extraHead = '', allowScripts = false, bodyClass = '' } = {},
|
||||
) {
|
||||
const csp = [
|
||||
"default-src 'none'",
|
||||
"style-src 'unsafe-inline'",
|
||||
@@ -45,12 +97,44 @@ function previewDocument(title, bodyHtml, { downloadUrl = null, extraHead = '' }
|
||||
"object-src 'self'",
|
||||
"base-uri 'none'",
|
||||
"form-action 'none'",
|
||||
"script-src 'none'",
|
||||
allowScripts ? "script-src 'unsafe-inline'" : "script-src 'none'",
|
||||
].join('; ');
|
||||
const downloadLink = downloadUrl
|
||||
? `<p class="meta"><a href="${escapeHtml(downloadUrl)}" target="_blank" rel="noopener noreferrer">下载原文件</a></p>`
|
||||
: '';
|
||||
return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta http-equiv="Content-Security-Policy" content="${csp}"><title>${escapeHtml(title)}</title><style>${PREVIEW_SHELL_STYLE}</style>${extraHead}</head><body><h1>${escapeHtml(title)}</h1>${downloadLink}${bodyHtml}</body></html>`;
|
||||
const bodyAttrs = bodyClass ? ` class="${escapeHtml(bodyClass)}"` : '';
|
||||
return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta http-equiv="Content-Security-Policy" content="${csp}"><title>${escapeHtml(title)}</title><style>${PREVIEW_SHELL_STYLE}</style>${extraHead}</head><body${bodyAttrs}><h1>${escapeHtml(title)}</h1>${downloadLink}${bodyHtml}</body></html>`;
|
||||
}
|
||||
|
||||
function renderImageLightboxMarkup({ downloadUrl, title, triggerClass = 'image-frame' }) {
|
||||
const safeUrl = escapeHtml(downloadUrl);
|
||||
const safeTitle = escapeHtml(title);
|
||||
return (
|
||||
`<img class="${escapeHtml(triggerClass)}" data-image-lightbox-trigger src="${safeUrl}" alt="${safeTitle}">` +
|
||||
`<div class="image-lightbox" hidden role="dialog" aria-modal="true" aria-label="图片预览">` +
|
||||
`<button type="button" class="image-lightbox-close" aria-label="关闭">${IMAGE_LIGHTBOX_CLOSE_ICON}</button>` +
|
||||
`<img src="${safeUrl}" alt="${safeTitle}">` +
|
||||
`</div>${IMAGE_LIGHTBOX_SCRIPT}`
|
||||
);
|
||||
}
|
||||
|
||||
export function wantsInlineImageViewer(req) {
|
||||
if (req?.query?.viewer === '1') return true;
|
||||
if (req?.query?.viewer === '0') return false;
|
||||
const fetchDest = String(req?.get?.('sec-fetch-dest') ?? req?.headers?.['sec-fetch-dest'] ?? '').toLowerCase();
|
||||
if (fetchDest === 'image') return false;
|
||||
if (fetchDest === 'document' || fetchDest === 'iframe') return true;
|
||||
const accept = String(req?.get?.('accept') ?? req?.headers?.accept ?? '');
|
||||
return /text\/html/i.test(accept);
|
||||
}
|
||||
|
||||
export function renderImageAssetViewerHtml({ asset, downloadUrl }) {
|
||||
const title = asset.displayName || asset.filename;
|
||||
return previewDocument(title, renderImageLightboxMarkup({ downloadUrl, title }), {
|
||||
downloadUrl,
|
||||
allowScripts: true,
|
||||
bodyClass: 'image-viewer',
|
||||
});
|
||||
}
|
||||
|
||||
function renderInlineMarkdown(text) {
|
||||
@@ -220,11 +304,10 @@ export function renderAssetPreviewHtml({ asset, buffer, downloadUrl }) {
|
||||
}
|
||||
|
||||
if (mimeType.startsWith('image/')) {
|
||||
return previewDocument(
|
||||
title,
|
||||
`<img class="image-frame" src="${escapeHtml(downloadUrl)}" alt="${escapeHtml(title)}">`,
|
||||
{ downloadUrl },
|
||||
);
|
||||
return previewDocument(title, renderImageLightboxMarkup({ downloadUrl, title }), {
|
||||
downloadUrl,
|
||||
allowScripts: true,
|
||||
});
|
||||
}
|
||||
|
||||
if (mimeType === 'text/csv') {
|
||||
|
||||
Reference in New Issue
Block a user