From e962d86ba5184f462fa24a8a28db48bfdb888e01 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 18 Sep 2026 09:29:30 +0800 Subject: [PATCH] feat(news-morning): enable comments, compact WeChat layout and SEO spec Default open WeChat draft comments, add SEO/visual template rules with h2 sections and mindspace-geo, and compress inline styles to pack more cards within the 20k HTML budget. Co-authored-by: Cursor --- ...trigger-news-morning-global-regenerate.mjs | 1 + wechat-daily-news-inline.mjs | 146 +++++++++++------- wechat-news-morning-draft.mjs | 30 +++- 3 files changed, 118 insertions(+), 59 deletions(-) diff --git a/scripts/trigger-news-morning-global-regenerate.mjs b/scripts/trigger-news-morning-global-regenerate.mjs index 560e172..011781b 100644 --- a/scripts/trigger-news-morning-global-regenerate.mjs +++ b/scripts/trigger-news-morning-global-regenerate.mjs @@ -35,6 +35,7 @@ async function main() { '以及 google / x / universities / global_markets。', '全页去重:同一 URL 或同一核心事件只允许出现一次;主栏目优先,扩展栏目必须换角度或跳过。', '禁止 id=hot 热搜栏目;禁止 .stats 或「X条资讯 · Y个栏目」统计行。', + 'SEO:title/description/mindspace-geo/faq 必做;section-title 内用 h2;card 左侧色条 + highlight 渐变。', '英文栏目标题可英文,每条必须有中文摘要与来源 URL;禁止整段纯英文正文。', ].join('\n'), }; diff --git a/wechat-daily-news-inline.mjs b/wechat-daily-news-inline.mjs index c2fd974..cba4184 100644 --- a/wechat-daily-news-inline.mjs +++ b/wechat-daily-news-inline.mjs @@ -27,6 +27,28 @@ const BRAND = Object.freeze({ onDarkSoft: '#e8c2c8', }); +/** 紧凑 inline 样式,降低 HTML 开销以便在 2 万字内塞入更多资讯条。 */ +const WX = Object.freeze({ + hero: `background:linear-gradient(135deg,${BRAND.red},${BRAND.navy});border-radius:14px;padding:22px 16px 20px;text-align:center;color:#fff`, + heroTitle: 'margin:0;font-size:24px;font-weight:700;line-height:1.35', + heroMeta: `margin:8px 0 0;font-size:12px;color:${BRAND.onDark}`, + heroSub: `margin:12px 0 0;font-size:12px;line-height:1.8;color:${BRAND.onDark};text-align:left`, + digest: `margin:12px 0 0;background:${BRAND.tintRed};border-radius:10px;padding:12px 14px`, + digestLabel: `margin:0 0 6px;font-size:11px;font-weight:700;color:${BRAND.red}`, + digestBody: `margin:0;font-size:12px;color:${BRAND.body};line-height:1.85`, + section: `margin:20px 0 10px;padding-bottom:6px;border-bottom:1px solid ${BRAND.line};font-size:16px;font-weight:700;color:${BRAND.ink}`, + feature: 'margin:0 0 12px;border-radius:12px;padding:14px 14px 12px;color:#fff', + featureTag: `margin:0 0 6px;font-size:10px;color:${BRAND.onDarkSoft}`, + featureTitle: 'margin:0;font-size:15px;font-weight:700;line-height:1.5', + featureBody: `margin:8px 0 0;font-size:13px;line-height:1.75;color:${BRAND.onDark}`, + listWrap: 'margin:0 0 12px', + listTitle: `margin:0;font-size:14px;font-weight:700;color:${BRAND.ink};line-height:1.5`, + listBody: `margin:5px 0 0;font-size:13px;color:${BRAND.body};line-height:1.75`, + rankWrap: `margin:0 0 12px;background:${BRAND.tintRed};border-radius:10px;padding:12px 12px 8px`, + rankTitle: `margin:0 0 8px;font-size:14px;font-weight:700;color:${BRAND.ink};line-height:1.5`, + rankBody: `margin:0;font-size:13px;color:${BRAND.body};line-height:1.85`, +}); + const SECTION_LABELS = { headlines: 'TOP NEWS', weather: 'WEATHER', @@ -204,25 +226,25 @@ function renderRankListBody(bodyHtml, { maxRows = 14 } = {}) { return `${String(index + 1).padStart(2, '0')} ${text}`; }) .join('
'); - return `

${lines}

`; + return `

${lines}

`; } function renderFeatureCard(cardHtml, { theme, title, body }) { const tag = extractTagLabel(cardHtml); return [ - `
`, - tag ? `

${tag}

` : '', - `

${title}

`, - body ? `

${body}

` : '', + `
`, + tag ? `

${tag}

` : '', + `

${title}

`, + body ? `

${body}

` : '', '
', ].filter(Boolean).join(''); } function renderListCard({ title, body, index }) { return [ - '
', - `

${formatCardIndex(index)} ${title}

`, - body ? `

${body}

` : '', + `
`, + `

${formatCardIndex(index)} ${title}

`, + body ? `

${body}

` : '', '
', ].filter(Boolean).join(''); } @@ -239,8 +261,8 @@ function renderCard(cardHtml, { const rankBody = theme ? '' : renderRankListBody(rawBody); if (rankBody) { return [ - `
`, - `

${stripInlineText(title)}

`, + `
`, + `

${stripInlineText(title)}

`, rankBody, '
', ].join(''); @@ -263,7 +285,7 @@ function renderSectionTitle(titleHtml, sectionId = '') { const suffix = label ? ` ${label}` : ''; - return `

${text}${suffix}

`; + return `

${text}${suffix}

`; } function resolveMappedImageUrl(src, imageUrlMap = new Map()) { @@ -292,9 +314,9 @@ function renderTodayDigest(html, { maxItems = 6, maxItemChars = 30 } = {}) { if (items.length < 2) return ''; const lines = items.map((item) => `· ${item}`).join('
'); return [ - `
`, - `

今日导读

`, - `

${lines}

`, + `
`, + `

今日导读

`, + `

${lines}

`, '
', ].join(''); } @@ -305,10 +327,10 @@ function renderHero(html, _imageUrlMap = new Map(), { includeSubtitle = true } = const dateBadge = stripInlineText(String(html).match(/
]*>([\s\S]*?)<\/div>/i)?.[1] ?? ''); const subtitle = includeSubtitle ? truncatePlainText(extractHeroSubtitle(html), 150) : ''; return [ - `
`, - `

${title.replace(/^[📰\s]+/u, '')}

`, - dateBadge ? `

${dateBadge}

` : '', - subtitle ? `

${subtitle}

` : '', + `
`, + `

${title.replace(/^[📰\s]+/u, '')}

`, + dateBadge ? `

${dateBadge}

` : '', + subtitle ? `

${subtitle}

` : '', '
', ].filter(Boolean).join(''); } @@ -562,34 +584,34 @@ function renderGenericSection( } const DEFAULT_SECTION_CARD_LIMITS = { - headlines: 6, - world: 4, - domestic: 4, - google: 2, - x: 2, - asia_pacific: 2, - commodities: 2, - ai_chips: 2, - policy: 2, - universities: 2, - global_markets: 2, - community: 2, - science: 2, - culture: 2, - startup: 2, + headlines: 7, + world: 5, + domestic: 5, + google: 3, + x: 3, + asia_pacific: 3, + commodities: 3, + ai_chips: 3, + policy: 3, + universities: 3, + global_markets: 3, + community: 3, + science: 3, + culture: 3, + startup: 3, crypto: 2, - geopolitics: 2, - local_life: 2, - official_feeds: 2, - finance: 4, - tech: 4, - sports: 2, + geopolitics: 3, + local_life: 3, + official_feeds: 3, + finance: 5, + tech: 5, + sports: 3, technews: 2, knowledge: 3, }; -const DEFAULT_CARD_BODY_CHARS = 150; -const MIN_CARDS_TO_KEEP = 12; +const DEFAULT_CARD_BODY_CHARS = 132; +const MIN_CARDS_TO_KEEP = 16; const BASE_TRIM_FLAGS = Object.freeze({ skipHistory: true, @@ -599,9 +621,18 @@ const BASE_TRIM_FLAGS = Object.freeze({ skipPageFooter: true, }); -/** 每档栏目容量,从「全量」到「精简」。 */ +function mergeSectionCardLimits(fullCounts = {}, caps = DEFAULT_SECTION_CARD_LIMITS) { + const merged = { ...caps }; + for (const [id, count] of Object.entries(fullCounts)) { + const cap = caps[id]; + merged[id] = cap == null ? count : Math.min(count, cap); + } + return merged; +} + +/** 每档栏目容量,从「较全量」到「精简」。 */ const CARD_LIMIT_TIERS = [ - null, + DEFAULT_SECTION_CARD_LIMITS, DEFAULT_SECTION_CARD_LIMITS, { ...DEFAULT_SECTION_CARD_LIMITS, headlines: 6, world: 4, domestic: 4, finance: 4, tech: 4, knowledge: 3 }, { headlines: 5, world: 3, domestic: 3, google: 2, finance: 3, tech: 3, sports: 2, technews: 1, knowledge: 3 }, @@ -609,7 +640,7 @@ const CARD_LIMIT_TIERS = [ ]; /** 每档卡片摘要字数,标题信息已足够时优先压缩摘要来换取更多条目。 */ -const BODY_CHAR_TIERS = [DEFAULT_CARD_BODY_CHARS, 132, 118, 104]; +const BODY_CHAR_TIERS = [DEFAULT_CARD_BODY_CHARS, 120, 108, 96]; const TRIM_PROFILES = CARD_LIMIT_TIERS.map((sectionCardLimits, index) => ({ ...BASE_TRIM_FLAGS, @@ -745,12 +776,11 @@ function renderFollowMpSection(qrcodeImageUrl = '') { } /** - * 内容卡(头条卡 / 列表卡 / 榜单卡 / 知识卡)统一以 `margin:0 0 {10|14|15|16}px` 开头, - * 其余区块(Hero 12px、配图 18px、天气 11px/6px、速览、栏目标题、底部)刻意用别的起始值, - * 便于按整卡统计与裁剪。调整任何一处 margin 都必须同步这里。 + * 内容卡统一以 `margin:0 0 12px` 开头(见 WX.listWrap / WX.feature / WX.rankWrap), + * 便于按整卡统计与裁剪。调整 margin 时必须同步这里。 */ -const INLINE_CARD_SECTION_RE = /
[\s\S]*?[\s\S]*?<\/section>/g; -const INLINE_SECTION_TITLE_PREFIX = '

[\s\S]*?[\s\S]*?<\/section>/g; +const INLINE_SECTION_TITLE_PREFIX = '

`:80~160 字,含日期 + 当日 3~5 条核心标题关键词(国内/国际/财经/科技至少各 1 个)。', + '3. `` JSON:summary(120~180 字)、keywords 数组(8~12 个:新闻/日期/国内/国际/财经/科技/体育/城市名/英文主题词)、faq 至少 3 组 q/a(分别覆盖国内、国际财经、科技体育)。', + '4. 每个 `.section-title` 内标题文字必须用 `

` 包裹(保留 icon span),便于搜索引擎理解栏目层级。', + '5. 每条 card 的 `

` 标题须含具体事件关键词,禁止「暂无」「未能获取」等空词。', + '6. footer 注明整理时间与主要来源域名,便于 GEO 引用。', +].join('\n'); /** 生产 daily-news-0915 版式:div.section + 扩展全球栏目。 */ export const NEWS_MORNING_TEMPLATE_0915_SPEC = [ @@ -42,7 +52,8 @@ export const NEWS_MORNING_TEMPLATE_0915_SPEC = [ '写页前可先 read_file 打开同目录最近 daily-news-*.html 或上述 URL 对应文件,对照 CSS 与 DOM 骨架;正文条目必须全部来自当日检索。', '1. 外层:`.wrap` > `.hero`(`.hero-content` 内 h1「📰 每日新闻早报」、`.date-badge`、`.subtitle` 一句主编导语)> `.container`;禁止 `.stats` 或「X条资讯 · Y个栏目」统计行。', '2. quick-links 固定顺序:📰 要闻|🌤️ 天气|🌍 国际|🇨🇳 国内|💰 财经|🤖 科技|🏆 体育|🔎 Google|𝕏 X|🌏 亚太|⛽ 能源|🧠 AI芯片|⚖️ 政策|🎓 高校|📈 全球股市|💬 社区|🔬 科学|🎬 文娱|🚀 创投|₿ 加密|🛡️ 地缘|🏙️ 城市|📡 权威快讯|📚 小知识|📅 历史(href 对应下方 id);禁止 id=hot / 🔥 热搜。', - '3. 区块容器必须用 `
`(禁止 `
`);`.section-title` 含 `` emoji。', + '3. 区块容器必须用 `
`(禁止 `
`);`.section-title` 含 `` emoji,标题文字用 `

` 包裹。', + '3b. 视觉精细化:`.card` 左侧 3px 色条(tag 色系);`.card.highlight-box` 渐变底+白字;`.quick-links` 横向滚动+浅灰底圆角;hero 保留渐变与 date-badge;整体留白充足、字号层级清晰(h1 28px / h2 18px / h3 16px / p 14px)。', '4. 区块 id 与内容:', ' - id=headlines:「今日要闻 · {完整日期}」;至少 3 条 `.card.highlight-box`(tag + h3 + p + source),标题 15~28 字、前 10 字有钩子。', ' - id=weather:「今日天气 · {月日}」;`.weather-today` + `.weather-grid` 8~12 张 `.weather-card`;热门城市(北京、上海、广州、深圳、成都、杭州、西安、重庆、武汉、南京、厦门、三亚等),禁止用区域预报顶替城市卡。', @@ -66,7 +77,7 @@ export const NEWS_MORNING_TEMPLATE_0915_SPEC = [ ' - id=knowledge:「📚 知识小卡片」;1~2 条短知识 card。', ' - id=history:「📅 近7天新闻早报」;列出近 7 天 daily-news-MMDD.html 链接。', '5. 每条 card:span.tag + h3 + p(2~4 句)+ div.source。', - '6. footer:新闻来源列表 + 整理时间 + TKMind 品牌;meta mindspace-cover、title、description;生成 daily-news-MMDD.thumbnail.png。', + '6. footer:新闻来源列表 + 整理时间 + TKMind 品牌;必须含 title、description、mindspace-cover、mindspace-geo;生成 daily-news-MMDD.thumbnail.png。', '7. 推送微信草稿时转为内联 style 版式(微信不支持