Update WeChat draft follow template
Memind CI / Test, build, and release guards (push) Failing after 2m51s

This commit is contained in:
john
2026-09-14 15:36:40 +08:00
parent 64b5819978
commit b6d85d487a
13 changed files with 302 additions and 16 deletions
+2 -2
View File
@@ -23,8 +23,8 @@
1. **Hero 实图**(若有):与页面 `mindspace-cover.cover` / hero 背景 / 侧车缩略图同源,经 `uploadimg` 上传
2. **页面正文**:按 profile 转换(诗页 / 散文 / 技能模板 / 通用语义提取)
3. **阅读原文**12px 小胶囊链接,位于正文末尾、关注区之前;有 `publicUrl` 时必须出现
4. **欢迎关注 TKMind 智趣** + 公众号二维码(无 logo、无额外导语
3. **阅读原文**:位于正文末尾、关注区之前;有 `publicUrl` 时必须出现。使用虚线引导卡片 + 上下箭头 + 主色按钮(文案仍为「阅读原文」,禁止回退旧版「点击阅读原文」大按钮)
4. **欢迎关注 TKMind 智趣** + 服务号二维码(约 120px,静态资源 `/assets/mp-follow-qrcode.png`,推送时优先 uploadimg 到微信 CDN
正文超过 `maxContentChars`(20000)时,**先给阅读原文和关注区留额度,再截正文**,禁止先拼再整体 `slice` 把页脚裁掉。公开页失败弹窗必须展示服务端真实原因,不能用「请先登录 MindSpace」兜底掩盖校验失败。
+3
View File
@@ -90,6 +90,9 @@ test('daily-news draft includes read original when publicUrl is present', () =>
const followIdx = article.content.indexOf('欢迎关注 TKMind 智趣');
assert.ok(readIdx >= 0, '缺少阅读原文');
assert.ok(readIdx < followIdx, '阅读原文应在关注区之前');
assert.match(article.content, /完整网页版更精彩/);
assert.match(article.content, /⬇ ⬇ ⬇/);
assert.match(article.content, /width:120px;height:120px/);
assert.match(article.content, /href="https:\/\/m\.tkmind\.cn\/u\/john\/pages\/tkmind-upgrade-sep2026"/);
assert.equal(article.contentSourceUrl, 'https://m.tkmind.cn/u/john/pages/tkmind-upgrade-sep2026');
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 38 KiB

@@ -0,0 +1,70 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>公众号草稿 layout 样板 · 八月午后 · 一首短诗</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background: #ededed;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif;
color: #111;
}
.phone {
max-width: 420px;
margin: 24px auto;
background: #fff;
border-radius: 18px;
overflow: hidden;
box-shadow: 0 12px 40px rgba(0,0,0,.12);
}
.status {
padding: 10px 16px;
background: #f7f7f7;
font-size: 12px;
color: #666;
border-bottom: 1px solid #eee;
}
.article {
padding: 18px 16px 24px;
line-height: 1.75;
font-size: 16px;
}
.article h1 {
margin: 0 0 12px;
font-size: 22px;
line-height: 1.35;
}
.article p { margin: 0 0 14px; color: #333; }
.meta {
max-width: 420px;
margin: 0 auto 24px;
padding: 0 8px;
font-size: 12px;
color: #666;
line-height: 1.6;
}
.meta code { background: rgba(0,0,0,.06); padding: 1px 4px; border-radius: 4px; }
</style>
</head>
<body>
<div class="phone">
<div class="status">微信草稿箱 · 排版样板(非真实推送)</div>
<article class="article">
<h1>八月午后 · 一首短诗</h1>
<p>August · Afternoon 八月午后 蝉声把正午拉得很长, 甜是慢的,光是慢的,连无聊都舍不得结束。 短诗 · 四节 2026 年 8 月 阅读约 40 秒 往 下 正文 切换竖排 壹 蝉声把正午拉得很长, 树荫在墙上,翻了个身。 一杯凉白开浮着半朵云, 风扇数着拍子: 一圈,又一圈 。 贰 阳光跨过门槛, 在地板上停住, 像一封没有署名的信。 猫睡成一座小岛, 梦里自有它的海。 叁 远处有人喊了句什么, 被热风揉软, 落在西瓜红色的边缘。 肆 甜是慢的,光是慢的 , 连一整个下午的无聊,都舍不得结束—— 傍晚终于来了, 它只是把影子拉长了一点, 轻声说:明天见。 一整天的漫长,其实只发生在 八月午后那两个小时里 。 蝉 声音的长度 蝉鸣没有起伏,所以时间失去了刻度。午后之所以长,不是因为无事可做,是因为没有东西在推进。 影 会翻身的树荫 墙上的影子一寸寸移动,是这一首诗里唯一诚实的钟表——它从不报时,只慢慢证明下午正在过去。 猫 睡在光斑里 猫总挑最暖的那块地板睡。它比谁都清楚:夏日的好,是可以用身体去接住的。 瓜 甜是慢的 远处的人</p>
<p>August · Afternoon 八月午后 蝉声把正午拉得很长, 甜是慢的,光是慢的,连无聊都舍不得结束。 短诗 · 四节 2026 年 8 月 阅读约 40 秒 往 下 正文 切换竖排 壹 蝉声把正午拉得很长, 树荫在墙上,翻了个身。 一杯凉白开浮着半朵云, 风扇数着拍子: 一圈,又一圈 。 贰 阳光跨过门槛, 在地板上停住, 像一封没有署名的信。 猫睡成一座小岛, 梦里自有它的海。 叁 远处有人喊了句什么, 被热风揉软, 落在西瓜红色的边缘。 肆 甜是慢的,光是慢的 , 连一整个下午的无聊,都舍不得结束—— 傍晚终于来了, 它只是把影子拉长了一点, 轻声说:明天见。 一整天的漫长,其实只发生在 八月午后那两个小时里 。 蝉 声音的长度 蝉鸣没有起伏,所以时间失去了刻度。午后之所以长,不是因为无事可做,是因为没有东西在推进。 影 会翻身的树荫 墙上的影子一寸寸移动,是这一首诗里唯一诚实的钟表——它从不报时,只慢慢证明下午正在过去。 猫 睡在光斑里 猫总挑最暖的那块地板睡。它比谁都清楚:夏日的好,是可以用身体去接住的。 瓜 甜是慢的 远处的人</p>
<section style="text-align:center;margin:26px 0 14px;padding:20px 16px 18px;background:linear-gradient(180deg,#eef4ff 0%,#fff 100%);border:2px dashed #576b95;border-radius:16px;box-shadow:0 6px 18px rgba(87,107,149,.12);"><p style="margin:0;font-size:14px;color:#4a5568;line-height:1.6;font-weight:600;">✨ 完整网页版更精彩</p><p style="margin:6px 0 0;font-size:12px;color:#718096;line-height:1.5;">动画 · 互动 · 高清大图 · 完整内容</p><p style="margin:14px 0 0;font-size:26px;line-height:1;color:#576b95;letter-spacing:6px;font-weight:700;">⬇ ⬇ ⬇</p><p style="margin:12px 0 0;"><a href="https://m.tkmind.cn/MindSpace/1c99b83b-0454-474f-a5d2-129d34506a32/public/august-afternoon.html" style="display:inline-block;padding:12px 32px;border-radius:999px;background:linear-gradient(135deg,#576b95,#3d5a80);color:#fff;font-size:16px;font-weight:700;text-decoration:none;box-shadow:0 6px 16px rgba(87,107,149,.35);">📖 阅读原文</a></p><p style="margin:12px 0 0;font-size:13px;color:#576b95;font-weight:700;line-height:1.5;">👆 点击上方按钮,查看完整精彩页面</p><p style="margin:8px 0 0;font-size:22px;line-height:1;color:#576b95;letter-spacing:4px;">➡️ ➡️ ➡️</p></section>
<section style="text-align:center;padding:22px 16px 18px;margin:18px 0 8px;background:linear-gradient(180deg,#fff8f8,#fff);border:1px solid #ffcdd2;border-radius:12px;"><p style="margin:0 0 6px;font-size:16px;font-weight:700;color:#b71c1c;line-height:1.5;">欢迎关注 TKMind 智趣</p><img src="https://m.tkmind.cn/assets/mp-follow-qrcode.png" alt="TKMind 服务号二维码" style="width:120px;height:120px;display:block;margin:12px auto 0;border-radius:10px;border:1px solid #eee;" /><p style="margin:12px 0 0;font-size:12px;color:#a0aec0;">长按扫描关注</p></section>
</article>
</div>
<div class="meta">
<p>阅读原文跳转:<code>https://m.tkmind.cn/MindSpace/1c99b83b-0454-474f-a5d2-129d34506a32/public/august-afternoon.html</code></p>
<p>二维码静态资源:<code>https://m.tkmind.cn/assets/mp-follow-qrcode.png</code></p>
<p><code>scripts/preview-wechat-draft-layout.mjs</code> 生成,用于确认「阅读原文」引导与关注区二维码尺寸。</p>
</div>
</body>
</html>
+144
View File
@@ -0,0 +1,144 @@
#!/usr/bin/env node
/**
* 生成公众号草稿底部 layout 样板页(阅读原文引导 + TKMind 关注二维码)。
*
* 用法:
* node scripts/preview-wechat-draft-layout.mjs [publicUrl] [outputPath]
*/
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import {
applyWechatDraftArticleLayout,
resolveMpFollowQrcodePublicUrl,
} from '../wechat-draft-article-layout.mjs';
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const userId = '1c99b83b-0454-474f-a5d2-129d34506a32';
const defaultPublicUrl = `https://m.tkmind.cn/MindSpace/${userId}/public/august-afternoon.html`;
async function loadSampleBody() {
const samplePath = path.join(root, 'MindSpace', userId, 'public', 'august-afternoon.html');
try {
const html = await fs.readFile(samplePath, 'utf8');
const titleMatch = html.match(/<title>([^<]+)<\/title>/i);
const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
const text = String(bodyMatch?.[1] ?? '')
.replace(/<script[\s\S]*?<\/script>/gi, '')
.replace(/<style[\s\S]*?<\/style>/gi, '')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.slice(0, 480);
return {
title: titleMatch?.[1]?.trim() || 'MindSpace 页面样板',
excerpt: text || '这是一段示例正文,推送到公众号后会自动转换为微信兼容格式。',
};
} catch {
return {
title: 'MindSpace 页面样板',
excerpt: '这是一段示例正文。完整网页版包含更丰富的排版、动画与互动内容,请点击下方「阅读原文」查看。',
};
}
}
function renderPreviewHtml({ title, excerpt, articleContent, publicUrl, qrcodeUrl }) {
return `<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>公众号草稿 layout 样板 · ${title}</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background: #ededed;
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif;
color: #111;
}
.phone {
max-width: 420px;
margin: 24px auto;
background: #fff;
border-radius: 18px;
overflow: hidden;
box-shadow: 0 12px 40px rgba(0,0,0,.12);
}
.status {
padding: 10px 16px;
background: #f7f7f7;
font-size: 12px;
color: #666;
border-bottom: 1px solid #eee;
}
.article {
padding: 18px 16px 24px;
line-height: 1.75;
font-size: 16px;
}
.article h1 {
margin: 0 0 12px;
font-size: 22px;
line-height: 1.35;
}
.article p { margin: 0 0 14px; color: #333; }
.meta {
max-width: 420px;
margin: 0 auto 24px;
padding: 0 8px;
font-size: 12px;
color: #666;
line-height: 1.6;
}
.meta code { background: rgba(0,0,0,.06); padding: 1px 4px; border-radius: 4px; }
</style>
</head>
<body>
<div class="phone">
<div class="status">微信草稿箱 · 排版样板(非真实推送)</div>
<article class="article">
<h1>${title}</h1>
<p>${excerpt}</p>
${articleContent}
</article>
</div>
<div class="meta">
<p>阅读原文跳转:<code>${publicUrl}</code></p>
<p>二维码静态资源:<code>${qrcodeUrl}</code></p>
<p>由 <code>scripts/preview-wechat-draft-layout.mjs</code> 生成,用于确认「阅读原文」引导与关注区二维码尺寸。</p>
</div>
</body>
</html>`;
}
async function main() {
const publicUrl = process.argv[2]?.trim() || defaultPublicUrl;
const outputPath = path.resolve(
process.argv[3] || path.join(root, 'public/dev/wechat-draft-layout-preview.html'),
);
const sample = await loadSampleBody();
const qrcodeUrl = resolveMpFollowQrcodePublicUrl(process.env);
const articleContent = applyWechatDraftArticleLayout(
`<p>${sample.excerpt}</p>`,
{ publicUrl, qrcodeImageUrl: qrcodeUrl },
);
const html = renderPreviewHtml({
title: sample.title,
excerpt: sample.excerpt,
articleContent,
publicUrl,
qrcodeUrl,
});
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, html, 'utf8');
console.log(`样板页已写入 ${outputPath}`);
console.log(`publicUrl: ${publicUrl}`);
console.log(`qrcode: ${qrcodeUrl}`);
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
+11 -3
View File
@@ -4,6 +4,7 @@ import { extractPageTitle } from './wechat/verify/share-preview-repair.mjs';
const MAX_WECHAT_CONTENT_CHARS = 20000;
const MAX_WECHAT_CONTENT_TARGET_CHARS = 19600;
const DEFAULT_PORTAL_URL = 'https://m.tkmind.cn';
const FOLLOW_QRCODE_SIZE_PX = 120;
const CARD_VARIANTS = {
'highlight-box': {
@@ -152,9 +153,16 @@ function renderHero(html, imageUrlMap = new Map()) {
function renderReadOriginalLink(publicUrl) {
if (!publicUrl) return '';
return [
'<p style="margin:18px 0 10px;text-align:center;line-height:1.6;">',
`<a href="${publicUrl}" style="display:inline-block;padding:5px 14px;border:1px solid #e2e8f0;border-radius:999px;color:#576b95;font-size:12px;text-decoration:none;background:#fafafa;">📖 阅读原文</a>`,
'<section style="text-align:center;margin:26px 0 14px;padding:20px 16px 18px;background:linear-gradient(180deg,#eef4ff 0%,#fff 100%);border:2px dashed #576b95;border-radius:16px;box-shadow:0 6px 18px rgba(87,107,149,.12);">',
'<p style="margin:0;font-size:14px;color:#4a5568;line-height:1.6;font-weight:600;">✨ 完整网页版更精彩</p>',
'<p style="margin:6px 0 0;font-size:12px;color:#718096;line-height:1.5;">动画 · 互动 · 高清大图 · 完整内容</p>',
'<p style="margin:14px 0 0;font-size:26px;line-height:1;color:#576b95;letter-spacing:6px;font-weight:700;">⬇ ⬇ ⬇</p>',
'<p style="margin:12px 0 0;">',
`<a href="${publicUrl}" style="display:inline-block;padding:12px 32px;border-radius:999px;background:linear-gradient(135deg,#576b95,#3d5a80);color:#fff;font-size:16px;font-weight:700;text-decoration:none;box-shadow:0 6px 16px rgba(87,107,149,.35);">📖 阅读原文</a>`,
'</p>',
'<p style="margin:12px 0 0;font-size:13px;color:#576b95;font-weight:700;line-height:1.5;">👆 点击上方按钮,查看完整精彩页面</p>',
'<p style="margin:8px 0 0;font-size:22px;line-height:1;color:#576b95;letter-spacing:4px;">➡️ ➡️ ➡️</p>',
'</section>',
].join('');
}
@@ -167,7 +175,7 @@ function renderFollowMpSection(qrcodeImageUrl = '') {
return [
'<section style="text-align:center;padding:22px 16px 18px;margin:18px 0 8px;background:linear-gradient(180deg,#fff8f8,#fff);border:1px solid #ffcdd2;border-radius:12px;">',
'<p style="margin:0 0 6px;font-size:16px;font-weight:700;color:#b71c1c;line-height:1.5;">欢迎关注 TKMind 智趣</p>',
`<img src="${qrcodeImageUrl}" alt="TKMind 服务号二维码" style="width:168px;height:168px;display:block;margin:14px auto 0;border-radius:10px;border:1px solid #eee;" />`,
`<img src="${qrcodeImageUrl}" alt="TKMind 服务号二维码" style="width:${FOLLOW_QRCODE_SIZE_PX}px;height:${FOLLOW_QRCODE_SIZE_PX}px;display:block;margin:12px auto 0;border-radius:10px;border:1px solid #eee;" />`,
'<p style="margin:12px 0 0;font-size:12px;color:#a0aec0;">长按扫描关注</p>',
'</section>',
].join('');
+38 -6
View File
@@ -14,6 +14,13 @@ import { WECHAT_DRAFT_PUBLICATION_STANDARD } from './wechat-draft-publication-st
const WECHAT_DRAFT_CONTENT_CHAR_LIMIT =
WECHAT_DRAFT_PUBLICATION_STANDARD.limits.maxContentChars;
const FOLLOW_QRCODE_SIZE_PX = 120;
const MP_FOLLOW_QRCODE_PUBLIC_PATH = '/assets/mp-follow-qrcode.png';
export function resolveMpFollowQrcodePublicUrl(env = process.env) {
const base = String(env.H5_PUBLIC_BASE_URL ?? 'https://m.tkmind.cn').replace(/\/$/, '');
return `${base}${MP_FOLLOW_QRCODE_PUBLIC_PATH}`;
}
export function clipWechatDraftBodyToFitFooter(
body,
@@ -35,9 +42,16 @@ export function clipWechatDraftBodyToFitFooter(
export function renderReadOriginalLink(publicUrl) {
if (!publicUrl) return '';
return [
'<p style="margin:18px 0 10px;text-align:center;line-height:1.6;">',
`<a href="${publicUrl}" style="display:inline-block;padding:5px 14px;border:1px solid #e2e8f0;border-radius:999px;color:#576b95;font-size:12px;text-decoration:none;background:#fafafa;">📖 阅读原文</a>`,
'<section style="text-align:center;margin:26px 0 14px;padding:20px 16px 18px;background:linear-gradient(180deg,#eef4ff 0%,#fff 100%);border:2px dashed #576b95;border-radius:16px;box-shadow:0 6px 18px rgba(87,107,149,.12);">',
'<p style="margin:0;font-size:14px;color:#4a5568;line-height:1.6;font-weight:600;">✨ 完整网页版更精彩</p>',
'<p style="margin:6px 0 0;font-size:12px;color:#718096;line-height:1.5;">动画 · 互动 · 高清大图 · 完整内容</p>',
'<p style="margin:14px 0 0;font-size:26px;line-height:1;color:#576b95;letter-spacing:6px;font-weight:700;">⬇ ⬇ ⬇</p>',
'<p style="margin:12px 0 0;">',
`<a href="${publicUrl}" style="display:inline-block;padding:12px 32px;border-radius:999px;background:linear-gradient(135deg,#576b95,#3d5a80);color:#fff;font-size:16px;font-weight:700;text-decoration:none;box-shadow:0 6px 16px rgba(87,107,149,.35);">📖 阅读原文</a>`,
'</p>',
'<p style="margin:12px 0 0;font-size:13px;color:#576b95;font-weight:700;line-height:1.5;">👆 点击上方按钮,查看完整精彩页面</p>',
'<p style="margin:8px 0 0;font-size:22px;line-height:1;color:#576b95;letter-spacing:4px;">➡️ ➡️ ➡️</p>',
'</section>',
].join('');
}
@@ -48,7 +62,7 @@ export function renderFollowTkMindSection({
qrcodeImageUrl = '',
} = {}) {
const qrcodeBlock = qrcodeImageUrl
? `<img src="${qrcodeImageUrl}" alt="TKMind 服务号二维码" style="width:168px;height:168px;display:block;margin:14px auto 0;border-radius:10px;border:1px solid #eee;" />`
? `<img src="${qrcodeImageUrl}" alt="TKMind 服务号二维码" style="width:${FOLLOW_QRCODE_SIZE_PX}px;height:${FOLLOW_QRCODE_SIZE_PX}px;display:block;margin:12px auto 0;border-radius:10px;border:1px solid #eee;" />`
: '';
return [
'<section style="text-align:center;padding:22px 16px 18px;margin:18px 0 8px;background:linear-gradient(180deg,#fff8f8,#fff);border:1px solid #ffcdd2;border-radius:12px;">',
@@ -84,23 +98,41 @@ export function resolveTkMindBrandIconPath(memindLibRoot = process.cwd()) {
return candidates.find((candidate) => fs.existsSync(candidate)) ?? null;
}
export function resolveMpFollowQrcodeAssetPath(memindLibRoot = process.cwd()) {
const fromHelper = resolveMpFollowQrcodePath(memindLibRoot);
if (fromHelper) return fromHelper;
const publicAsset = path.join(memindLibRoot, 'public', MP_FOLLOW_QRCODE_PUBLIC_PATH.replace(/^\//, ''));
if (fs.existsSync(publicAsset)) return publicAsset;
const cwdPublicAsset = path.join(process.cwd(), 'public', MP_FOLLOW_QRCODE_PUBLIC_PATH.replace(/^\//, ''));
return fs.existsSync(cwdPublicAsset) ? cwdPublicAsset : null;
}
export async function uploadWechatDraftBrandAssets({
accessToken,
wechatFetch = undiciFetch,
memindLibRoot = process.cwd(),
env = process.env,
} = {}) {
const qrcodePath = resolveMpFollowQrcodeAssetPath(memindLibRoot);
const publicFallbackUrl = resolveMpFollowQrcodePublicUrl(env);
let qrcodeImageUrl = '';
if (!accessToken) {
if (!qrcodePath) {
return { qrcodeImageUrl };
}
const qrcodePath = resolveMpFollowQrcodePath(memindLibRoot);
if (qrcodePath) {
if (!accessToken) {
return { qrcodeImageUrl: publicFallbackUrl };
}
try {
qrcodeImageUrl = await uploadWechatArticleContentImage(
accessToken,
fs.readFileSync(qrcodePath),
{ wechatFetch, filename: 'mp-follow-qrcode.png' },
);
} catch {
qrcodeImageUrl = publicFallbackUrl;
}
return { qrcodeImageUrl };
+1 -1
View File
@@ -19,7 +19,7 @@ export const WECHAT_DRAFT_PUBLICATION_STANDARD = Object.freeze({
readOriginal: Object.freeze({
position: 'after-content-before-footer',
label: '阅读原文',
style: 'compact-link-12px',
style: 'guided-card-with-arrows',
forbidLegacyLargeButton: true,
}),
followFooter: Object.freeze({
+24 -2
View File
@@ -5,6 +5,7 @@ import test from 'node:test';
import {
applyWechatDraftArticleLayout,
clipWechatDraftBodyToFitFooter,
resolveMpFollowQrcodePublicUrl,
} from './wechat-draft-article-layout.mjs';
import {
buildMindSpacePageWechatDraftArticleForPush,
@@ -29,6 +30,27 @@ test('standard version is pinned', () => {
assert.equal(WECHAT_DRAFT_PUBLICATION_STANDARD.cover.priority[0], 'hero-raster');
});
test('applyWechatDraftArticleLayout renders guided read-original section and smaller qrcode', () => {
const content = applyWechatDraftArticleLayout('<p>正文</p>', {
publicUrl: PUBLIC_URL,
qrcodeImageUrl: 'https://m.tkmind.cn/assets/mp-follow-qrcode.png',
});
assert.match(content, /完整网页版更精彩/);
assert.match(content, /⬇ ⬇ ⬇/);
assert.match(content, /点击上方按钮,查看完整精彩页面/);
assert.match(content, /width:120px;height:120px/);
assert.match(content, /📖 阅读原文/);
const check = verifyWechatDraftPublicationContent(content, { publicUrl: PUBLIC_URL });
assert.equal(check.ok, true, check.issues.join('; '));
});
test('resolveMpFollowQrcodePublicUrl points to platform asset path', () => {
assert.equal(
resolveMpFollowQrcodePublicUrl({ H5_PUBLIC_BASE_URL: 'https://m.tkmind.cn' }),
'https://m.tkmind.cn/assets/mp-follow-qrcode.png',
);
});
test('verifyWechatDraftPublicationContent rejects legacy large read button', () => {
const bad = applyWechatDraftArticleLayout('<p>正文</p>', {
publicUrl: PUBLIC_URL,
@@ -81,13 +103,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, /https:\/\/m\.tkmind\.cn\/assets\/mp-follow-qrcode\.png/);
const readIdx = article.content.indexOf('阅读原文');
const followIdx = article.content.indexOf('欢迎关注 TKMind 智趣');
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 应在阅读原文之前');
assert.doesNotMatch(article.content.slice(0, readIdx), /mp-follow-qrcode/);
}
});
+4 -2
View File
@@ -282,7 +282,9 @@ export function extractDailyNewsArticleMeta(html) {
export function resolveMpFollowQrcodePath(memindLibRoot = process.cwd()) {
const candidates = [
path.join(memindLibRoot, 'wechat/assets/mp-follow-qrcode.png'),
path.join(memindLibRoot, 'public/assets/mp-follow-qrcode.png'),
path.join(process.cwd(), 'wechat/assets/mp-follow-qrcode.png'),
path.join(process.cwd(), 'public/assets/mp-follow-qrcode.png'),
];
return candidates.find((candidate) => fs.existsSync(candidate)) ?? null;
}
@@ -442,10 +444,10 @@ export async function buildDailyNewsWechatDraftArticleForPush({
});
}
export function convertNewsPageHtmlToWechatArticle(html, { publicUrl = '' } = {}) {
export function convertNewsPageHtmlToWechatArticle(html, { publicUrl = '', qrcodeImageUrl = '' } = {}) {
const dailyNews = isDailyNewsFormat(html);
if (dailyNews) {
return buildDailyNewsWechatDraftArticle({ html, publicUrl });
return buildDailyNewsWechatDraftArticle({ html, publicUrl, qrcodeImageUrl });
}
const title = extractPageTitle(html) || '今日新闻热点分析';
const digest = (extractMetaDescription(html) || title).slice(0, 120);
+5
View File
@@ -85,12 +85,17 @@ function createPool(seedRow = null) {
test('convertNewsPageHtmlToWechatArticle converts daily-news to inline html', () => {
const article = convertNewsPageHtmlToWechatArticle(SAMPLE_HTML, {
publicUrl: 'https://m.tkmind.cn/MindSpace/demo/public/daily-news-0910.html',
qrcodeImageUrl: 'https://m.tkmind.cn/assets/mp-follow-qrcode.png',
});
assert.match(article.title, /2026年9月10日/u);
assert.equal(article.contentMode, 'inline_html');
assert.match(article.content, /每日新闻早报/u);
assert.match(article.content, /示例热点/u);
assert.match(article.content, /阅读原文/u);
assert.match(article.content, /完整网页版更精彩/u);
assert.match(article.content, /⬇ ⬇ ⬇/u);
assert.match(article.content, /width:120px;height:120px/u);
assert.match(article.content, /mp-follow-qrcode\.png/u);
assert.match(article.content, /一起创作/u);
assert.match(article.content, /点我/u);
assert.doesNotMatch(article.content, /近7天新闻早报/u);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 38 KiB