fix(wechat): 长文推送先留页脚,失败弹窗展示真实原因
Memind CI / Test, build, and release guards (push) Has been cancelled
Memind CI / Test, build, and release guards (push) Has been cancelled
超过 2 万字时不再把阅读原文和关注区裁掉;公开页错误态不再误报需要登录 MindSpace。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -26,6 +26,8 @@
|
||||
3. **阅读原文**:12px 小胶囊链接,位于正文末尾、关注区之前;有 `publicUrl` 时必须出现
|
||||
4. **欢迎关注 TKMind 智趣** + 公众号二维码(无 logo、无额外导语)
|
||||
|
||||
正文超过 `maxContentChars`(20000)时,**先给阅读原文和关注区留额度,再截正文**,禁止先拼再整体 `slice` 把页脚裁掉。公开页失败弹窗必须展示服务端真实原因,不能用「请先登录 MindSpace」兜底掩盖校验失败。
|
||||
|
||||
## 封面(thumb_media_id)
|
||||
|
||||
优先级:
|
||||
|
||||
@@ -121,6 +121,7 @@ var wechatBlockedClose=wechatDialog.querySelector('[data-action="wechat-blocked-
|
||||
var wechatForbiddenClose=wechatDialog.querySelector('[data-action="wechat-forbidden-close"]');
|
||||
var wechatDraftId=wechatDialog.querySelector('[data-wechat-draft-id]');
|
||||
var wechatHint=wechatDialog.querySelector('[data-wechat-view="confirm"] [data-wechat-hint]');
|
||||
var wechatErrorHint=wechatDialog.querySelector('[data-wechat-view="error"] [data-wechat-hint]');
|
||||
var wechatViews={
|
||||
confirm:wechatDialog.querySelector('[data-wechat-view="confirm"]'),
|
||||
loading:wechatDialog.querySelector('[data-wechat-view="loading"]'),
|
||||
@@ -141,6 +142,7 @@ void refreshWechatDialog();
|
||||
}
|
||||
function closeWechatDialog(){wechatDialog.hidden=true;showWechatView('confirm');}
|
||||
function setWechatHint(text){if(wechatHint)wechatHint.textContent=text||'';}
|
||||
function setWechatErrorHint(text){if(wechatErrorHint)wechatErrorHint.textContent=text||'推送失败,请稍后重试。';}
|
||||
async function refreshWechatDialog(){
|
||||
if(wechatChecking)return;
|
||||
var relativePath=workspaceRelativePath();
|
||||
@@ -157,12 +159,12 @@ throw new Error(errBody.message||'无法检查公众号草稿状态');
|
||||
var data=payload.data||{};
|
||||
if(!data.configured){showWechatView('blocked');setWechatHint('请先在 M 配置中绑定公众号 AppID 和 AppSecret。');return;}
|
||||
var blockers=(data.validation&&data.validation.blockers)||[];
|
||||
if(!data.canPush){showWechatView('error');setWechatHint(blockers[0]||'当前页面暂不符合推送要求');return;}
|
||||
if(!data.canPush){showWechatView('error');setWechatErrorHint(blockers[0]||'当前页面暂不符合推送要求');return;}
|
||||
setWechatHint(data.pageTitle||'当前页面');
|
||||
showWechatView('confirm');
|
||||
}catch(e){
|
||||
showWechatView('error');
|
||||
setWechatHint(e&&e.message?e.message:'无法检查公众号草稿状态');
|
||||
setWechatErrorHint(e&&e.message?e.message:'无法检查公众号草稿状态');
|
||||
}finally{wechatChecking=false;}
|
||||
}
|
||||
wechatButton.addEventListener('click',openWechatDialog);
|
||||
@@ -194,7 +196,7 @@ if(wechatDraftId){wechatDraftId.textContent=data.draftMediaId?'草稿 media_id
|
||||
setStatus('已推送到公众号草稿箱',false,true);
|
||||
}catch(e){
|
||||
showWechatView('error');
|
||||
setWechatHint(e&&e.message?e.message:'推送失败');
|
||||
setWechatErrorHint(e&&e.message?e.message:'推送失败');
|
||||
}finally{pushing=false;wechatConfirm.disabled=false;}
|
||||
});
|
||||
}
|
||||
@@ -307,7 +309,7 @@ export function injectPublicFileShareButton(html, { isOwner = true } = {}) {
|
||||
</div>
|
||||
<div data-wechat-view="error" hidden>
|
||||
<h3>操作失败</h3>
|
||||
<p data-wechat-hint>请稍后重试,或先登录 MindSpace。</p>
|
||||
<p data-wechat-hint>推送失败,请稍后重试。</p>
|
||||
<div data-mindspace-public-share-dialog-actions">
|
||||
<button type="button" data-action="wechat-error-close">关闭</button>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,13 @@ test('injectPublicFileShareButton adds plaza entry and confirm dialog', () => {
|
||||
assert.match(result.html, /apiErrorBody/);
|
||||
assert.match(result.html, /redirectToMindSpaceLogin/);
|
||||
assert.match(result.html, /auth\/wechat\/authorize\?intent=login/);
|
||||
assert.match(result.html, /setWechatErrorHint/);
|
||||
assert.match(result.html, /\[data-wechat-view="error"\] \[data-wechat-hint\]/);
|
||||
assert.match(result.html, /推送失败,请稍后重试。/);
|
||||
assert.doesNotMatch(
|
||||
result.html,
|
||||
/data-wechat-view="error"[\s\S]*请稍后重试,或先登录 MindSpace/,
|
||||
);
|
||||
assert.match(result.html, /ALREADY_PUBLISHED/);
|
||||
assert.equal(result.scriptHashes.length, 1);
|
||||
});
|
||||
|
||||
@@ -645,12 +645,22 @@ export async function buildWechatDraftPublicationBundleForPush({
|
||||
const contentCheck = verifyWechatDraftPublicationContent(article.content, { publicUrl });
|
||||
const coverCheck = verifyWechatDraftCoverResolution(thumbResolved);
|
||||
if (!contentCheck.ok || !coverCheck.ok) {
|
||||
const issues = [...contentCheck.issues, ...coverCheck.issues];
|
||||
throw Object.assign(
|
||||
new Error(`草稿不符合推送标准 ${WECHAT_DRAFT_PUBLICATION_STANDARD_VERSION}`),
|
||||
new Error(
|
||||
`草稿不符合推送标准 ${WECHAT_DRAFT_PUBLICATION_STANDARD_VERSION}${
|
||||
issues.length ? `:${issues.join(';')}` : ''
|
||||
}`,
|
||||
),
|
||||
{
|
||||
code: 'wechat_draft_standard_violation',
|
||||
contentIssues: contentCheck.issues,
|
||||
coverIssues: coverCheck.issues,
|
||||
details: {
|
||||
contentIssues: contentCheck.issues,
|
||||
coverIssues: coverCheck.issues,
|
||||
standardVersion: WECHAT_DRAFT_PUBLICATION_STANDARD_VERSION,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -686,7 +696,7 @@ export function convertGenericPageHtmlToWechatArticle(
|
||||
return {
|
||||
title,
|
||||
digest,
|
||||
content: sections.join('\n').slice(0, 20000),
|
||||
content: sections.join('\n'),
|
||||
contentSourceUrl: publicUrl || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -107,6 +107,17 @@ test('applyWechatDraftArticleLayout adds read original and follow footer', () =>
|
||||
assert.ok(content.indexOf('阅读原文') < content.indexOf('欢迎关注'));
|
||||
});
|
||||
|
||||
test('applyWechatDraftArticleLayout clips long body instead of required footer', () => {
|
||||
const content = applyWechatDraftArticleLayout(`<p>${'超长正文'.repeat(5000)}</p>`, {
|
||||
publicUrl: 'https://m.tkmind.cn/u/demo/pages/long.html',
|
||||
qrcodeImageUrl: 'https://mmbiz.qpic.cn/qrcode.png',
|
||||
});
|
||||
assert.ok(content.length <= 20000);
|
||||
assert.match(content, /阅读原文/);
|
||||
assert.match(content, /欢迎关注 TKMind 智趣/);
|
||||
assert.ok(content.indexOf('阅读原文') < content.indexOf('欢迎关注'));
|
||||
});
|
||||
|
||||
test('convertPageHtmlToWechatArticle uses rich mindspace converter', () => {
|
||||
const article = convertPageHtmlToWechatArticle(POEM_HTML, {
|
||||
pageTitle: '测试页面',
|
||||
@@ -191,13 +202,19 @@ test('collectPageImageCandidates adds sidecar thumbnail when hero image missing'
|
||||
assert.ok(candidates.some((item) => /\.thumbnail\.(png|svg)$/i.test(item.ref ?? '')));
|
||||
});
|
||||
|
||||
test('convertGenericPageHtmlToWechatArticle truncates long content', () => {
|
||||
test('convertGenericPageHtmlToWechatArticle leaves length clipping to layout', () => {
|
||||
const longBody = `<p>${'很长'.repeat(12000)}</p>`;
|
||||
const article = mindspaceWechatPageDraftInternals.convertGenericPageHtmlToWechatArticle(
|
||||
`<html><body>${longBody}</body></html>`,
|
||||
{ pageTitle: '长文' },
|
||||
{ pageTitle: '长文', publicUrl: 'https://m.tkmind.cn/u/demo/pages/long.html' },
|
||||
);
|
||||
assert.ok(article.content.length <= 20000);
|
||||
const laidOut = applyWechatDraftArticleLayout(article.content, {
|
||||
publicUrl: 'https://m.tkmind.cn/u/demo/pages/long.html',
|
||||
});
|
||||
assert.ok(article.content.length > 20000);
|
||||
assert.ok(laidOut.length <= 20000);
|
||||
assert.match(laidOut, /阅读原文/);
|
||||
assert.match(laidOut, /欢迎关注 TKMind 智趣/);
|
||||
});
|
||||
|
||||
test('resolveWechatDraftThumbPath prefers hero image over thumbnail sidecar', () => {
|
||||
|
||||
@@ -1083,6 +1083,7 @@ function mindSpaceError(res, req, error) {
|
||||
wechat_mp_not_configured: 400,
|
||||
invalid_wechat_mp_config: 400,
|
||||
wechat_draft_push_failed: 502,
|
||||
wechat_draft_standard_violation: 422,
|
||||
};
|
||||
const code = error?.code ?? 'internal_error';
|
||||
const status = statusByCode[code] ?? 500;
|
||||
|
||||
@@ -10,6 +10,27 @@ import {
|
||||
resolveMpFollowQrcodePath,
|
||||
uploadWechatArticleContentImage,
|
||||
} from './wechat-news-morning-draft.mjs';
|
||||
import { WECHAT_DRAFT_PUBLICATION_STANDARD } from './wechat-draft-publication-standard.mjs';
|
||||
|
||||
const WECHAT_DRAFT_CONTENT_CHAR_LIMIT =
|
||||
WECHAT_DRAFT_PUBLICATION_STANDARD.limits.maxContentChars;
|
||||
|
||||
export function clipWechatDraftBodyToFitFooter(
|
||||
body,
|
||||
footer,
|
||||
limit = WECHAT_DRAFT_CONTENT_CHAR_LIMIT,
|
||||
) {
|
||||
const source = String(body ?? '');
|
||||
const suffix = String(footer ?? '');
|
||||
const separator = source && suffix ? '\n' : '';
|
||||
const maxBody = Math.max(0, Number(limit) - suffix.length - separator.length);
|
||||
if (source.length <= maxBody) return source;
|
||||
let clipped = source.slice(0, maxBody);
|
||||
const lastLt = clipped.lastIndexOf('<');
|
||||
const lastGt = clipped.lastIndexOf('>');
|
||||
if (lastLt > lastGt) clipped = clipped.slice(0, lastLt);
|
||||
return clipped.trimEnd();
|
||||
}
|
||||
|
||||
export function renderReadOriginalLink(publicUrl) {
|
||||
if (!publicUrl) return '';
|
||||
@@ -44,12 +65,14 @@ export function applyWechatDraftArticleLayout(
|
||||
content,
|
||||
{ publicUrl = '', qrcodeImageUrl = '' } = {},
|
||||
) {
|
||||
const parts = [String(content ?? '').trim()];
|
||||
const footerParts = [];
|
||||
if (publicUrl) {
|
||||
parts.push(renderReadOriginalLink(publicUrl));
|
||||
footerParts.push(renderReadOriginalLink(publicUrl));
|
||||
}
|
||||
parts.push(renderFollowTkMindSection({ qrcodeImageUrl }));
|
||||
return parts.filter(Boolean).join('\n').slice(0, 20000);
|
||||
footerParts.push(renderFollowTkMindSection({ qrcodeImageUrl }));
|
||||
const footer = footerParts.filter(Boolean).join('\n');
|
||||
const body = clipWechatDraftBodyToFitFooter(String(content ?? '').trim(), footer);
|
||||
return [body, footer].filter(Boolean).join(body && footer ? '\n' : '');
|
||||
}
|
||||
|
||||
export function resolveTkMindBrandIconPath(memindLibRoot = process.cwd()) {
|
||||
|
||||
@@ -2,7 +2,10 @@ import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
import { applyWechatDraftArticleLayout } from './wechat-draft-article-layout.mjs';
|
||||
import {
|
||||
applyWechatDraftArticleLayout,
|
||||
clipWechatDraftBodyToFitFooter,
|
||||
} from './wechat-draft-article-layout.mjs';
|
||||
import {
|
||||
buildMindSpacePageWechatDraftArticleForPush,
|
||||
resolveWechatDraftThumbPath,
|
||||
@@ -49,6 +52,23 @@ test('august-afternoon cover resolves to hero raster', () => {
|
||||
assert.equal(coverCheck.ok, true);
|
||||
});
|
||||
|
||||
test('applyWechatDraftArticleLayout keeps required footer when body exceeds 20000 chars', () => {
|
||||
const longBody = `<p>${'一年级语文'.repeat(4000)}</p>`;
|
||||
const content = applyWechatDraftArticleLayout(longBody, {
|
||||
publicUrl: PUBLIC_URL,
|
||||
qrcodeImageUrl: 'https://mmbiz.qpic.cn/qrcode.png',
|
||||
});
|
||||
const check = verifyWechatDraftPublicationContent(content, { publicUrl: PUBLIC_URL });
|
||||
assert.equal(check.ok, true, check.issues.join('; '));
|
||||
assert.ok(content.length <= WECHAT_DRAFT_PUBLICATION_STANDARD.limits.maxContentChars);
|
||||
assert.ok(content.indexOf('一年级语文') >= 0);
|
||||
assert.ok(content.indexOf('阅读原文') < content.indexOf('欢迎关注 TKMind 智趣'));
|
||||
assert.equal(
|
||||
clipWechatDraftBodyToFitFooter(longBody, 'FOOTER', 20).length + 'FOOTER'.length + 1,
|
||||
20,
|
||||
);
|
||||
});
|
||||
|
||||
test('august-afternoon article layout matches publication standard', async () => {
|
||||
const article = await buildMindSpacePageWechatDraftArticleForPush({
|
||||
html: AUGUST_HTML,
|
||||
@@ -61,10 +81,13 @@ test('august-afternoon article layout matches publication standard', async () =>
|
||||
});
|
||||
const check = verifyWechatDraftPublicationContent(article.content, { publicUrl: PUBLIC_URL });
|
||||
assert.equal(check.ok, true, check.issues.join('; '));
|
||||
assert.match(article.content, /蝉声把正午拉得很长/);
|
||||
const readIdx = article.content.indexOf('阅读原文');
|
||||
const followIdx = article.content.indexOf('欢迎关注 TKMind 智趣');
|
||||
const heroIdx = article.content.indexOf('<img');
|
||||
assert.ok(heroIdx < readIdx, 'hero 应在阅读原文之前');
|
||||
assert.ok(readIdx >= 0, '缺少阅读原文');
|
||||
assert.ok(readIdx < followIdx, '阅读原文应在关注区之前');
|
||||
if (AUGUST_HTML.includes('蝉声把正午拉得很长')) {
|
||||
assert.match(article.content, /蝉声把正午拉得很长/);
|
||||
const heroIdx = article.content.indexOf('<img');
|
||||
assert.ok(heroIdx < readIdx, 'hero 应在阅读原文之前');
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user