Update WeChat draft follow template
Memind CI / Test, build, and release guards (push) Failing after 2m51s
Memind CI / Test, build, and release guards (push) Failing after 2m51s
This commit is contained in:
@@ -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」兜底掩盖校验失败。
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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('');
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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/);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 |
Reference in New Issue
Block a user