Add smart ACK provider for WeChat MP replies
Replace fixed ackText with a rule-based AckProvider that picks response templates by message type and intent (translate, summary, rewrite, poster, ppt, mindmap, code, search, schedule). Pure sync, zero I/O, auto-falls back to config.ackText on any error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+126
-13
@@ -1,3 +1,6 @@
|
||||
import { appendPlazaEmbedQuery } from './plaza-embed.mjs';
|
||||
import { resolvePlazaCoverUrl } from './plaza-posts.mjs';
|
||||
|
||||
function plazaError(message, code) {
|
||||
return Object.assign(new Error(message), { code });
|
||||
}
|
||||
@@ -44,7 +47,9 @@ export function buildPublicationUrl(publicUrl, portalBase) {
|
||||
export function renderPlazaPostRedirectHtml(post, targetUrl, { preview = false } = {}) {
|
||||
const title = escapePublicHtml(post.title);
|
||||
const summary = escapePublicHtml(post.summary);
|
||||
const iframeUrl = appendPlazaEmbedQuery(targetUrl);
|
||||
const safeTarget = escapePublicHtml(targetUrl);
|
||||
const safeIframeUrl = escapePublicHtml(iframeUrl);
|
||||
const previewNote = preview
|
||||
? '<p class="preview">开发预览:该帖子尚未审核通过,仅本地可见。</p>'
|
||||
: '';
|
||||
@@ -54,20 +59,39 @@ export function renderPlazaPostRedirectHtml(post, targetUrl, { preview = false }
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="refresh" content="0;url=${safeTarget}">
|
||||
<title>${title}</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: #f6f3eb;
|
||||
color: #17221d;
|
||||
font: 16px/1.7 ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
.post-shell {
|
||||
width: min(1180px, calc(100% - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 24px 0 48px;
|
||||
}
|
||||
.post-topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.post-back {
|
||||
color: #405048;
|
||||
text-decoration: none;
|
||||
font-weight: 700;
|
||||
}
|
||||
.post-open {
|
||||
color: #8f6b2f;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
main {
|
||||
width: min(560px, calc(100% - 32px));
|
||||
width: 100%;
|
||||
padding: 28px;
|
||||
border-radius: 24px;
|
||||
background: rgba(255, 253, 247, 0.92);
|
||||
@@ -83,16 +107,97 @@ export function renderPlazaPostRedirectHtml(post, targetUrl, { preview = false }
|
||||
background: rgba(143, 107, 47, 0.08);
|
||||
color: #6f5528;
|
||||
}
|
||||
.post-frame {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
border: 0;
|
||||
border-radius: 18px;
|
||||
background: #fff;
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.post-shell {
|
||||
width: min(100%, calc(100% - 20px));
|
||||
padding: 14px 0 24px;
|
||||
}
|
||||
.post-topbar {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
main {
|
||||
padding: 18px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
.post-frame {
|
||||
border-radius: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>${title}</h1>
|
||||
${summary ? `<p>${summary}</p>` : ''}
|
||||
${previewNote}
|
||||
<p>正在跳转到内容页…</p>
|
||||
<p><a href="${safeTarget}">若未自动跳转,请点击此处</a></p>
|
||||
</main>
|
||||
<div class="post-shell">
|
||||
<div class="post-topbar">
|
||||
<a class="post-back" href="/plaza">返回广场</a>
|
||||
<a class="post-open" href="${safeTarget}">单独打开原页面</a>
|
||||
</div>
|
||||
<main>
|
||||
<h1>${title}</h1>
|
||||
${summary ? `<p>${summary}</p>` : ''}
|
||||
${previewNote}
|
||||
<iframe class="post-frame" title="${title}" src="${safeIframeUrl}" sandbox="allow-scripts"></iframe>
|
||||
<p><a href="${safeTarget}">若页面未正常加载,可点击这里直接打开</a></p>
|
||||
</main>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var frame = document.querySelector('.post-frame');
|
||||
if (!frame) return;
|
||||
function applyHeight(height) {
|
||||
frame.style.height = Math.max(Number(height) || 0, window.innerHeight, 420) + 'px';
|
||||
}
|
||||
function measureFrameDocument() {
|
||||
try {
|
||||
var doc = frame.contentDocument;
|
||||
var body = doc && doc.body;
|
||||
var root = doc && doc.documentElement;
|
||||
if (!body && !root) return 0;
|
||||
var measured = Math.max(
|
||||
body ? body.scrollHeight : 0,
|
||||
body ? body.offsetHeight : 0,
|
||||
root ? root.scrollHeight : 0,
|
||||
root ? root.offsetHeight : 0
|
||||
);
|
||||
if (body) {
|
||||
for (var i = 0; i < body.children.length; i += 1) {
|
||||
var child = body.children[i];
|
||||
var rect = child.getBoundingClientRect();
|
||||
measured = Math.max(measured, rect.bottom + (root ? root.scrollTop : 0));
|
||||
}
|
||||
}
|
||||
return Math.ceil(measured);
|
||||
} catch (error) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
function syncFrameHeight() {
|
||||
applyHeight(measureFrameDocument() || frame.offsetHeight || window.innerHeight);
|
||||
}
|
||||
frame.addEventListener('load', function () {
|
||||
syncFrameHeight();
|
||||
[120, 400, 1000, 2000].forEach(function (delay) {
|
||||
window.setTimeout(syncFrameHeight, delay);
|
||||
});
|
||||
});
|
||||
window.addEventListener('message', function (event) {
|
||||
if (event.source && event.source !== frame.contentWindow) return;
|
||||
if (!event || !event.data || event.data.type !== 'plaza:embed-section') return;
|
||||
if (event.data.height) applyHeight(event.data.height);
|
||||
});
|
||||
window.addEventListener('resize', function () {
|
||||
syncFrameHeight();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
@@ -126,10 +231,12 @@ export function attachPlazaPublicRoutes(app, { pool, portalBase, devPreview = fa
|
||||
);
|
||||
const [postRows] = await pool.query(
|
||||
`SELECT pp.id, pp.title, pp.summary, pp.cover_url, pp.published_at,
|
||||
pr.public_url AS public_url,
|
||||
c.name AS category_name, c.slug AS category_slug, c.icon AS category_icon,
|
||||
u.slug AS user_slug, u.display_name AS user_display_name
|
||||
FROM plaza_posts pp
|
||||
JOIN plaza_categories c ON c.id = pp.category_id
|
||||
JOIN h5_publish_records pr ON pr.id = pp.publication_id
|
||||
JOIN h5_users u ON u.id = pp.user_id
|
||||
WHERE pp.status = 'published'
|
||||
ORDER BY pp.hot_score DESC, pp.id DESC
|
||||
@@ -142,8 +249,12 @@ export function attachPlazaPublicRoutes(app, { pool, portalBase, devPreview = fa
|
||||
const summary = escapePublicHtml(post.summary);
|
||||
const author = escapePublicHtml(post.user_display_name || post.user_slug);
|
||||
const category = escapePublicHtml(`${post.category_icon ?? ''} ${post.category_name ?? ''}`.trim());
|
||||
const coverUrl = escapePublicHtml(resolvePlazaCoverUrl(post.cover_url, post.public_url));
|
||||
const href = `/plaza/p/${encodeURIComponent(post.id)}`;
|
||||
return `<a class="card" href="${href}">
|
||||
<div class="card-cover">
|
||||
<img src="${coverUrl}" alt="${title} 封面预览" loading="lazy" />
|
||||
</div>
|
||||
<div class="card-top"><span>${category}</span><span>${author}</span></div>
|
||||
<h2>${title}</h2>
|
||||
${summary ? `<p>${summary}</p>` : ''}
|
||||
@@ -176,7 +287,9 @@ export function attachPlazaPublicRoutes(app, { pool, portalBase, devPreview = fa
|
||||
.categories { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 20px; }
|
||||
.categories a { text-decoration: none; color: #405048; border: 1px solid rgba(23,34,29,.12); border-radius: 999px; padding: 8px 12px; background: rgba(255,255,255,.6); }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; margin-top: 32px; }
|
||||
.card { display: flex; flex-direction: column; gap: 14px; min-height: 220px; padding: 22px; border-radius: 24px; text-decoration: none; color: inherit; background: rgba(255,253,247,.92); border: 1px solid rgba(23,34,29,.08); box-shadow: 0 20px 60px rgba(45,53,47,.08); }
|
||||
.card { display: flex; flex-direction: column; gap: 14px; min-height: 0; padding: 22px; border-radius: 24px; text-decoration: none; color: inherit; background: rgba(255,253,247,.92); border: 1px solid rgba(23,34,29,.08); box-shadow: 0 20px 60px rgba(45,53,47,.08); }
|
||||
.card-cover { overflow: hidden; aspect-ratio: 3 / 4; border-radius: 18px; background: linear-gradient(160deg, #f3eee6 0%, #e7efe9 100%); }
|
||||
.card-cover img { display: block; width: 100%; height: 100%; object-fit: cover; }
|
||||
.card:hover { transform: translateY(-2px); }
|
||||
.card-top, .card-foot { display: flex; justify-content: space-between; gap: 12px; font: 600 12px/1.4 ui-sans-serif, sans-serif; color: #6a746f; text-transform: uppercase; letter-spacing: .08em; }
|
||||
.card h2 { margin: 0; font-size: 24px; line-height: 1.1; letter-spacing: -.03em; }
|
||||
|
||||
Reference in New Issue
Block a user