weflow-cli 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +105 -0
- package/CHANGELOG.md +121 -0
- package/CONTRIBUTING.md +45 -0
- package/LICENSE +21 -21
- package/OPERATIONS.md +272 -0
- package/README.en.md +281 -0
- package/README.md +334 -149
- package/SECURITY.md +47 -0
- package/bin/weflow-cli.ts +4050 -581
- package/dist/bin/weflow-cli.js +4310 -588
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/mcp-server/index.js +178 -236
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/core/dbPathService.d.ts +15 -1
- package/dist/src/core/dbPathService.d.ts.map +1 -1
- package/dist/src/core/dbPathService.js +177 -19
- package/dist/src/core/dbPathService.js.map +1 -1
- package/dist/src/core/keyService.d.ts +8 -0
- package/dist/src/core/keyService.d.ts.map +1 -1
- package/dist/src/core/keyService.js +127 -22
- package/dist/src/core/keyService.js.map +1 -1
- package/dist/src/core/ntCore.d.ts +59 -1
- package/dist/src/core/ntCore.d.ts.map +1 -1
- package/dist/src/core/ntCore.js +201 -65
- package/dist/src/core/ntCore.js.map +1 -1
- package/dist/src/core/sqlcipherCore.d.ts.map +1 -1
- package/dist/src/core/sqlcipherCore.js +18 -1
- package/dist/src/core/sqlcipherCore.js.map +1 -1
- package/dist/src/core/wcdbCore.d.ts.map +1 -1
- package/dist/src/core/wcdbCore.js +3 -6
- package/dist/src/core/wcdbCore.js.map +1 -1
- package/dist/src/services/assistantDaemon.d.ts +19 -0
- package/dist/src/services/assistantDaemon.d.ts.map +1 -0
- package/dist/src/services/assistantDaemon.js +117 -0
- package/dist/src/services/assistantDaemon.js.map +1 -0
- package/dist/src/services/assistantMemory.d.ts +36 -0
- package/dist/src/services/assistantMemory.d.ts.map +1 -0
- package/dist/src/services/assistantMemory.js +182 -0
- package/dist/src/services/assistantMemory.js.map +1 -0
- package/dist/src/services/assistantPrivacy.d.ts +25 -0
- package/dist/src/services/assistantPrivacy.d.ts.map +1 -0
- package/dist/src/services/assistantPrivacy.js +77 -0
- package/dist/src/services/assistantPrivacy.js.map +1 -0
- package/dist/src/services/assistantRouting.d.ts +18 -0
- package/dist/src/services/assistantRouting.d.ts.map +1 -0
- package/dist/src/services/assistantRouting.js +68 -0
- package/dist/src/services/assistantRouting.js.map +1 -0
- package/dist/src/services/assistantService.d.ts +37 -0
- package/dist/src/services/assistantService.d.ts.map +1 -0
- package/dist/src/services/assistantService.js +284 -0
- package/dist/src/services/assistantService.js.map +1 -0
- package/dist/src/services/assistantTools.d.ts +24 -0
- package/dist/src/services/assistantTools.d.ts.map +1 -0
- package/dist/src/services/assistantTools.js +582 -0
- package/dist/src/services/assistantTools.js.map +1 -0
- package/dist/src/services/chatService.d.ts +44 -0
- package/dist/src/services/chatService.d.ts.map +1 -1
- package/dist/src/services/chatService.js +105 -4
- package/dist/src/services/chatService.js.map +1 -1
- package/dist/src/services/configService.d.ts +55 -0
- package/dist/src/services/configService.d.ts.map +1 -1
- package/dist/src/services/configService.js +147 -6
- package/dist/src/services/configService.js.map +1 -1
- package/dist/src/services/evidenceService.d.ts +30 -0
- package/dist/src/services/evidenceService.d.ts.map +1 -0
- package/dist/src/services/evidenceService.js +98 -0
- package/dist/src/services/evidenceService.js.map +1 -0
- package/dist/src/services/exportService.d.ts +13 -20
- package/dist/src/services/exportService.d.ts.map +1 -1
- package/dist/src/services/exportService.js +113 -47
- package/dist/src/services/exportService.js.map +1 -1
- package/dist/src/services/initKeyService.d.ts +19 -0
- package/dist/src/services/initKeyService.d.ts.map +1 -0
- package/dist/src/services/initKeyService.js +80 -0
- package/dist/src/services/initKeyService.js.map +1 -0
- package/dist/src/services/messageContract.d.ts +23 -0
- package/dist/src/services/messageContract.d.ts.map +1 -0
- package/dist/src/services/messageContract.js +49 -0
- package/dist/src/services/messageContract.js.map +1 -0
- package/dist/src/services/messageQuery.d.ts +7 -0
- package/dist/src/services/messageQuery.d.ts.map +1 -0
- package/dist/src/services/messageQuery.js +30 -0
- package/dist/src/services/messageQuery.js.map +1 -0
- package/dist/src/services/wechat-formatter.js +14 -14
- package/dist/src/services/wechatMessageService.d.ts.map +1 -1
- package/dist/src/services/wechatMessageService.js +31 -3
- package/dist/src/services/wechatMessageService.js.map +1 -1
- package/dist/src/services/whitelistService.d.ts +52 -4
- package/dist/src/services/whitelistService.d.ts.map +1 -1
- package/dist/src/services/whitelistService.js +183 -17
- package/dist/src/services/whitelistService.js.map +1 -1
- package/dist/src/types.d.ts +20 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/dateRange.d.ts +15 -0
- package/dist/src/utils/dateRange.d.ts.map +1 -0
- package/dist/src/utils/dateRange.js +48 -0
- package/dist/src/utils/dateRange.js.map +1 -0
- package/dist/src/utils/mcpSecurity.d.ts +5 -0
- package/dist/src/utils/mcpSecurity.d.ts.map +1 -0
- package/dist/src/utils/mcpSecurity.js +39 -0
- package/dist/src/utils/mcpSecurity.js.map +1 -0
- package/dist/src/utils/packageRoot.d.ts +2 -0
- package/dist/src/utils/packageRoot.d.ts.map +1 -0
- package/dist/src/utils/packageRoot.js +16 -0
- package/dist/src/utils/packageRoot.js.map +1 -0
- package/dist/src/utils/python.d.ts +2 -0
- package/dist/src/utils/python.d.ts.map +1 -0
- package/dist/src/utils/python.js +72 -0
- package/dist/src/utils/python.js.map +1 -0
- package/dist/src/utils/pythonProcessEnv.d.ts +3 -0
- package/dist/src/utils/pythonProcessEnv.d.ts.map +1 -0
- package/dist/src/utils/pythonProcessEnv.js +46 -0
- package/dist/src/utils/pythonProcessEnv.js.map +1 -0
- package/dist/src/utils/talkerUtils.d.ts +4 -1
- package/dist/src/utils/talkerUtils.d.ts.map +1 -1
- package/dist/src/utils/talkerUtils.js +9 -9
- package/dist/src/utils/talkerUtils.js.map +1 -1
- package/dist/src/utils/wechatEmoji.d.ts +5 -0
- package/dist/src/utils/wechatEmoji.d.ts.map +1 -0
- package/dist/src/utils/wechatEmoji.js +35 -0
- package/dist/src/utils/wechatEmoji.js.map +1 -0
- package/docs/AI_INTERFACE.md +189 -0
- package/docs/BRANCHES.md +87 -0
- package/docs/DATA_CONTRACT.md +74 -0
- package/docs/DECISIONS.md +270 -0
- package/docs/DEEPSEEK_V4_FLASH/346/226/275/345/267/245/346/226/207/346/241/243.md +234 -0
- package/docs/EVIDENCE_GUIDE.md +96 -0
- package/docs/MCP.md +95 -0
- package/docs/PARTNERS.md +41 -0
- package/docs/PROJECT_STATE.md +96 -0
- package/docs/RELEASING.md +124 -0
- package/docs/ROADMAP.md +191 -0
- package/docs/SETUP.md +103 -0
- package/docs/images/weflow-architecture-gpt-image-2.png +0 -0
- package/docs/images/weflow-architecture.png +0 -0
- package/docs/images/weflow-architecture.svg +65 -0
- package/mcp-server/index.ts +169 -269
- package/package.json +81 -72
- package/requirements-3x.txt +4 -0
- package/requirements-voice.txt +15 -0
- package/requirements.txt +9 -0
- package/resources/js/marked.min.js +69 -0
- package/resources/wechat-emoji/666.png +0 -0
- package/resources/wechat-emoji/Awesome.png +0 -0
- package/resources/wechat-emoji/Concerned.png +0 -0
- package/resources/wechat-emoji/Cry.png +0 -0
- package/resources/wechat-emoji/Emm.png +0 -0
- package/resources/wechat-emoji/Facepalm.png +0 -0
- package/resources/wechat-emoji/Grin.png +0 -0
- package/resources/wechat-emoji/OK.png +0 -0
- package/resources/wechat-emoji/Respect.png +0 -0
- package/resources/wechat-emoji/Sick.png +0 -0
- package/resources/wechat-emoji/Sleep.png +0 -0
- package/resources/wechat-emoji/Smile.png +0 -0
- package/resources/wechat-emoji//344/272/262/344/272/262.png +0 -0
- package/resources/wechat-emoji//344/276/277/344/276/277.png +0 -0
- package/resources/wechat-emoji//345/201/267/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/202/262/346/205/242.png +0 -0
- package/resources/wechat-emoji//345/206/215/350/247/201.png +0 -0
- package/resources/wechat-emoji//345/207/213/350/260/242.png +0 -0
- package/resources/wechat-emoji//345/212/240/346/262/271.png +0 -0
- package/resources/wechat-emoji//345/213/276/345/274/225.png +0 -0
- package/resources/wechat-emoji//345/217/221/345/221/206.png +0 -0
- package/resources/wechat-emoji//345/217/221/346/200/222.png +0 -0
- package/resources/wechat-emoji//345/217/221/346/212/226.png +0 -0
- package/resources/wechat-emoji//345/217/257/346/200/234.png +0 -0
- package/resources/wechat-emoji//345/217/263/345/223/274/345/223/274.png +0 -0
- package/resources/wechat-emoji//345/217/271/346/260/224.png +0 -0
- package/resources/wechat-emoji//345/220/203/347/223/234.png +0 -0
- package/resources/wechat-emoji//345/220/210/345/215/201.png +0 -0
- package/resources/wechat-emoji//345/220/220.png +0 -0
- package/resources/wechat-emoji//345/221/262/347/211/231.png +0 -0
- package/resources/wechat-emoji//345/222/222/351/252/202.png +0 -0
- package/resources/wechat-emoji//345/222/226/345/225/241.png +0 -0
- package/resources/wechat-emoji//345/223/207.png +0 -0
- package/resources/wechat-emoji//345/225/244/351/205/222.png +0 -0
- package/resources/wechat-emoji//345/230/230.png +0 -0
- package/resources/wechat-emoji//345/230/264/345/224/207.png +0 -0
- package/resources/wechat-emoji//345/230/277/345/223/210.png +0 -0
- package/resources/wechat-emoji//345/233/247.png +0 -0
- package/resources/wechat-emoji//345/233/260.png +0 -0
- package/resources/wechat-emoji//345/235/217/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/244/247/345/223/255.png +0 -0
- package/resources/wechat-emoji//345/244/251/345/225/212.png +0 -0
- package/resources/wechat-emoji//345/244/252/351/230/263.png +0 -0
- package/resources/wechat-emoji//345/244/261/346/234/233.png +0 -0
- package/resources/wechat-emoji//345/245/270/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/245/275/347/232/204.png +0 -0
- package/resources/wechat-emoji//345/247/224/345/261/210.png +0 -0
- package/resources/wechat-emoji//345/256/263/347/276/236.png +0 -0
- package/resources/wechat-emoji//345/260/264/345/260/254.png +0 -0
- package/resources/wechat-emoji//345/272/206/347/245/235.png +0 -0
- package/resources/wechat-emoji//345/274/261.png +0 -0
- package/resources/wechat-emoji//345/274/272.png +0 -0
- package/resources/wechat-emoji//345/276/227/346/204/217.png +0 -0
- package/resources/wechat-emoji//345/276/256/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/277/203/347/242/216.png +0 -0
- package/resources/wechat-emoji//345/277/253/345/223/255/344/272/206.png +0 -0
- package/resources/wechat-emoji//346/201/220/346/203/247.png +0 -0
- package/resources/wechat-emoji//346/202/240/351/227/262.png +0 -0
- package/resources/wechat-emoji//346/203/212/346/201/220.png +0 -0
- package/resources/wechat-emoji//346/203/212/350/256/266.png +0 -0
- package/resources/wechat-emoji//346/204/211/345/277/253.png +0 -0
- package/resources/wechat-emoji//346/206/250/347/254/221.png +0 -0
- package/resources/wechat-emoji//346/211/223/350/204/270.png +0 -0
- package/resources/wechat-emoji//346/212/223/347/213/202.png +0 -0
- package/resources/wechat-emoji//346/212/240/351/274/273.png +0 -0
- package/resources/wechat-emoji//346/212/261/346/213/263.png +0 -0
- package/resources/wechat-emoji//346/213/245/346/212/261.png +0 -0
- package/resources/wechat-emoji//346/213/263/345/244/264.png +0 -0
- package/resources/wechat-emoji//346/215/202/350/204/270.png +0 -0
- package/resources/wechat-emoji//346/217/241/346/211/213.png +0 -0
- package/resources/wechat-emoji//346/222/207/345/230/264.png +0 -0
- package/resources/wechat-emoji//346/223/246/346/261/227.png +0 -0
- package/resources/wechat-emoji//346/225/262/346/211/223.png +0 -0
- package/resources/wechat-emoji//346/227/240/350/257/255.png +0 -0
- package/resources/wechat-emoji//346/227/272/346/237/264.png +0 -0
- package/resources/wechat-emoji//346/231/225.png +0 -0
- package/resources/wechat-emoji//346/234/210/344/272/256.png +0 -0
- package/resources/wechat-emoji//346/234/272/346/231/272.png +0 -0
- package/resources/wechat-emoji//346/261/227.png +0 -0
- package/resources/wechat-emoji//346/265/201/346/263/252.png +0 -0
- package/resources/wechat-emoji//347/202/270/345/274/271.png +0 -0
- package/resources/wechat-emoji//347/203/237/350/212/261.png +0 -0
- package/resources/wechat-emoji//347/210/206/347/253/271.png +0 -0
- package/resources/wechat-emoji//347/210/261/345/277/203.png +0 -0
- package/resources/wechat-emoji//347/214/252/345/244/264.png +0 -0
- package/resources/wechat-emoji//347/216/253/347/221/260.png +0 -0
- package/resources/wechat-emoji//347/224/237/347/227/205.png +0 -0
- package/resources/wechat-emoji//347/226/221/351/227/256.png +0 -0
- package/resources/wechat-emoji//347/231/274.png +0 -0
- package/resources/wechat-emoji//347/231/275/347/234/274.png +0 -0
- package/resources/wechat-emoji//347/232/261/347/234/211.png +0 -0
- package/resources/wechat-emoji//347/235/241.png +0 -0
- package/resources/wechat-emoji//347/240/264/346/266/225/344/270/272/347/254/221.png +0 -0
- package/resources/wechat-emoji//347/244/274/347/211/251.png +0 -0
- package/resources/wechat-emoji//347/244/276/344/274/232/347/244/276/344/274/232.png +0 -0
- package/resources/wechat-emoji//347/246/217.png +0 -0
- package/resources/wechat-emoji//347/254/221/350/204/270.png +0 -0
- package/resources/wechat-emoji//347/272/242/345/214/205.png +0 -0
- package/resources/wechat-emoji//347/277/273/347/231/275/347/234/274.png +0 -0
- package/resources/wechat-emoji//350/200/266.png +0 -0
- package/resources/wechat-emoji//350/203/234/345/210/251.png +0 -0
- package/resources/wechat-emoji//350/204/270/347/272/242.png +0 -0
- package/resources/wechat-emoji//350/211/262.png +0 -0
- package/resources/wechat-emoji//350/213/246/346/266/251.png +0 -0
- package/resources/wechat-emoji//350/217/234/345/210/200.png +0 -0
- package/resources/wechat-emoji//350/233/213/347/263/225.png +0 -0
- package/resources/wechat-emoji//350/241/260.png +0 -0
- package/resources/wechat-emoji//350/243/202/345/274/200.png +0 -0
- package/resources/wechat-emoji//350/256/251/346/210/221/347/234/213/347/234/213.png +0 -0
- package/resources/wechat-emoji//350/260/203/347/232/256.png +0 -0
- package/resources/wechat-emoji//350/267/263/350/267/263.png +0 -0
- package/resources/wechat-emoji//350/275/254/345/234/210.png +0 -0
- package/resources/wechat-emoji//351/204/231/350/247/206.png +0 -0
- package/resources/wechat-emoji//351/227/255/345/230/264.png +0 -0
- package/resources/wechat-emoji//351/230/264/351/231/251.png +0 -0
- package/resources/wechat-emoji//351/232/276/350/277/207.png +0 -0
- package/resources/wechat-emoji//351/252/267/351/253/205.png +0 -0
- package/resources/wechat-emoji//351/274/223/346/216/214.png +0 -0
- package/scripts/_batch_link_sources.py +73 -73
- package/scripts/_utils.py +29 -0
- package/scripts/annual_report.py +796 -796
- package/scripts/auto_tag.py +128 -128
- package/scripts/biz_daily.py +210 -53
- package/scripts/chat_report.py +10 -0
- package/scripts/chat_stats.py +702 -702
- package/scripts/classify_daily.py +17 -11
- package/scripts/create_reading_notes.py +370 -370
- package/scripts/daily_stats.py +132 -0
- package/scripts/enrich_backlinks.py +163 -163
- package/scripts/export_chat_html.py +1704 -107
- package/scripts/extract_todos.py +445 -404
- package/scripts/fav_server.py +119 -12
- package/scripts/fix_topics.py +111 -111
- package/scripts/generate_ai_report.py +1 -1
- package/scripts/generate_html.py +597 -277
- package/scripts/inspection_records.py +354 -0
- package/scripts/mcp_bridge.py +398 -398
- package/scripts/nt_decrypt.py +702 -48
- package/scripts/pipeline.py +27 -19
- package/scripts/promote_all.py +312 -310
- package/scripts/promote_ideas.py +299 -297
- package/scripts/rag_chat.py +194 -191
- package/scripts/semantic_search.py +435 -431
- package/scripts/sync_fav.py +201 -146
- package/scripts/sync_weread.py +192 -192
- package/scripts/vault_rag.py +140 -139
- package/scripts/vault_search.py +141 -141
- package/scripts/wechat_emoji.py +132 -0
- package/scripts/wechat_emoticon.py +433 -0
- package/scripts/wechat_image.py +258 -0
- package/scripts/wechat_voice.py +327 -0
- package/src/core/dbPathService.ts +163 -18
- package/src/core/keyService.ts +137 -25
- package/src/core/ntCore.ts +215 -62
- package/src/core/sqlcipherCore.ts +17 -1
- package/src/core/wcdbCore.ts +3 -7
- package/src/services/assistantDaemon.ts +111 -0
- package/src/services/assistantMemory.ts +193 -0
- package/src/services/assistantPrivacy.ts +87 -0
- package/src/services/assistantRouting.ts +92 -0
- package/src/services/assistantService.ts +295 -0
- package/src/services/assistantTools.ts +549 -0
- package/src/services/chatService.ts +133 -4
- package/src/services/configService.ts +194 -6
- package/src/services/evidenceService.ts +129 -0
- package/src/services/exportService.ts +113 -48
- package/src/services/initKeyService.ts +83 -0
- package/src/services/messageContract.ts +77 -0
- package/src/services/messageQuery.ts +38 -0
- package/src/services/wechat-formatter.ts +348 -348
- package/src/services/wechatMessageService.ts +25 -3
- package/src/services/wereadService.ts +308 -308
- package/src/services/whitelistService.ts +209 -22
- package/src/types.ts +20 -0
- package/src/utils/dateRange.ts +58 -0
- package/src/utils/mcpSecurity.ts +39 -0
- package/src/utils/packageRoot.ts +14 -0
- package/src/utils/python.ts +73 -0
- package/src/utils/pythonProcessEnv.ts +45 -0
- package/src/utils/talkerUtils.ts +14 -2
- package/dist/src/utils/errors.d.ts +0 -25
- package/dist/src/utils/errors.d.ts.map +0 -1
- package/dist/src/utils/errors.js +0 -43
- package/dist/src/utils/errors.js.map +0 -1
- package/dist/src/utils/pythonRunner.d.ts +0 -27
- package/dist/src/utils/pythonRunner.d.ts.map +0 -1
- package/dist/src/utils/pythonRunner.js +0 -62
- package/dist/src/utils/pythonRunner.js.map +0 -1
- package/src/utils/errors.ts +0 -42
- package/src/utils/pythonRunner.ts +0 -81
package/scripts/generate_html.py
CHANGED
|
@@ -6,12 +6,31 @@
|
|
|
6
6
|
python scripts/generate_html.py --date 2026-05-18
|
|
7
7
|
python scripts/generate_html.py --date 2026-05-18 --output custom.html
|
|
8
8
|
"""
|
|
9
|
-
import sys, os, json, re
|
|
9
|
+
import sys, os, json, re, shutil
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from datetime import datetime
|
|
12
12
|
|
|
13
13
|
SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
14
|
-
|
|
14
|
+
PROJECT_ROOT = os.path.dirname(SCRIPTS_DIR)
|
|
15
|
+
SOURCE_ROOT = os.path.join(PROJECT_ROOT, 'output', 'biz-daily')
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _inline_marked_js(html_path: str):
|
|
19
|
+
"""Replace CDN marked.js with local inline copy to eliminate network dependency."""
|
|
20
|
+
marked_js_path = os.path.join(PROJECT_ROOT, 'resources', 'js', 'marked.min.js')
|
|
21
|
+
if not os.path.exists(marked_js_path):
|
|
22
|
+
print(f' [WARN] marked.js not found: {marked_js_path}')
|
|
23
|
+
return
|
|
24
|
+
with open(marked_js_path, 'r', encoding='utf-8') as f:
|
|
25
|
+
marked_js = f.read()
|
|
26
|
+
with open(html_path, 'r', encoding='utf-8') as f:
|
|
27
|
+
html = f.read()
|
|
28
|
+
cdn_tag = '<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>'
|
|
29
|
+
inline_tag = '<script>' + marked_js + '</script>'
|
|
30
|
+
if cdn_tag in html:
|
|
31
|
+
html = html.replace(cdn_tag, inline_tag, 1)
|
|
32
|
+
with open(html_path, 'w', encoding='utf-8') as f:
|
|
33
|
+
f.write(html)
|
|
15
34
|
|
|
16
35
|
TOPIC_LABELS = {
|
|
17
36
|
'AI': ('AI', '#8b5cf6', '🤖'),
|
|
@@ -19,16 +38,17 @@ TOPIC_LABELS = {
|
|
|
19
38
|
'新闻': ('新闻', '#f59e0b', '📰'),
|
|
20
39
|
'文学': ('文学', '#ec4899', '📝'),
|
|
21
40
|
'投资': ('投资', '#10b981', '💰'),
|
|
41
|
+
'政治': ('政治', '#0f766e', '🏛️'),
|
|
22
42
|
}
|
|
23
43
|
|
|
24
|
-
TOPIC_ORDER = ['AI', '学术', '新闻', '文学', '投资']
|
|
44
|
+
TOPIC_ORDER = ['AI', '学术', '新闻', '文学', '投资', '政治']
|
|
25
45
|
|
|
26
46
|
|
|
27
47
|
def parse_frontmatter(text: str) -> dict:
|
|
28
48
|
"""解析 YAML frontmatter(简易版,兼容现有格式)。"""
|
|
29
49
|
if not text.startswith('---'):
|
|
30
50
|
return {}
|
|
31
|
-
end = text.find('---', 3)
|
|
51
|
+
end = text.find('\n---', 3)
|
|
32
52
|
if end == -1:
|
|
33
53
|
return {}
|
|
34
54
|
fm_text = text[3:end].strip()
|
|
@@ -48,8 +68,8 @@ def parse_frontmatter(text: str) -> dict:
|
|
|
48
68
|
def get_body_preview(text: str, max_chars=200) -> str:
|
|
49
69
|
"""提取正文预览(跳过 frontmatter 和标题行)。"""
|
|
50
70
|
if text.startswith('---'):
|
|
51
|
-
end = text.find('---', 3)
|
|
52
|
-
text = text[end +
|
|
71
|
+
end = text.find('\n---', 3)
|
|
72
|
+
text = text[end + 4:] if end != -1 else text
|
|
53
73
|
lines = text.strip().split('\n')
|
|
54
74
|
body_lines = []
|
|
55
75
|
for line in lines:
|
|
@@ -106,6 +126,209 @@ def escape_html(text: str) -> str:
|
|
|
106
126
|
return text.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
|
107
127
|
|
|
108
128
|
|
|
129
|
+
def simple_markdown_to_html(md_text: str) -> str:
|
|
130
|
+
"""轻量 Markdown 转 HTML:支持标题、加粗、列表、引用、复选框、链接、分隔线。"""
|
|
131
|
+
import re
|
|
132
|
+
lines = md_text.split('\n')
|
|
133
|
+
html_parts = []
|
|
134
|
+
in_list = False
|
|
135
|
+
in_ul = False
|
|
136
|
+
in_blockquote = False
|
|
137
|
+
|
|
138
|
+
def close_lists():
|
|
139
|
+
nonlocal in_list, in_ul
|
|
140
|
+
if in_list:
|
|
141
|
+
html_parts.append('</ol>')
|
|
142
|
+
in_list = False
|
|
143
|
+
if in_ul:
|
|
144
|
+
html_parts.append('</ul>')
|
|
145
|
+
in_ul = False
|
|
146
|
+
|
|
147
|
+
def close_blockquote():
|
|
148
|
+
nonlocal in_blockquote
|
|
149
|
+
if in_blockquote:
|
|
150
|
+
html_parts.append('</blockquote>')
|
|
151
|
+
in_blockquote = False
|
|
152
|
+
|
|
153
|
+
for line in lines:
|
|
154
|
+
stripped = line.strip()
|
|
155
|
+
|
|
156
|
+
# 空行
|
|
157
|
+
if not stripped:
|
|
158
|
+
close_lists()
|
|
159
|
+
close_blockquote()
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
# 分隔线
|
|
163
|
+
if re.match(r'^---+$', stripped):
|
|
164
|
+
close_lists()
|
|
165
|
+
close_blockquote()
|
|
166
|
+
html_parts.append('<hr>')
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
# 标题
|
|
170
|
+
h_match = re.match(r'^(#{1,4})\s+(.+)$', stripped)
|
|
171
|
+
if h_match:
|
|
172
|
+
close_lists()
|
|
173
|
+
close_blockquote()
|
|
174
|
+
level = len(h_match.group(1))
|
|
175
|
+
text = h_match.group(2)
|
|
176
|
+
text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text)
|
|
177
|
+
html_parts.append(f'<h{level}>{text}</h{level}>')
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
# 引用
|
|
181
|
+
if stripped.startswith('> '):
|
|
182
|
+
close_lists()
|
|
183
|
+
if not in_blockquote:
|
|
184
|
+
html_parts.append('<blockquote>')
|
|
185
|
+
in_blockquote = True
|
|
186
|
+
text = stripped[2:]
|
|
187
|
+
text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text)
|
|
188
|
+
text = re.sub(r'\[(.+?)\]\((.+?)\)', r'<a href="\2">\1</a>', text)
|
|
189
|
+
html_parts.append(f'<p>{text}</p>')
|
|
190
|
+
continue
|
|
191
|
+
else:
|
|
192
|
+
close_blockquote()
|
|
193
|
+
|
|
194
|
+
# 有序列表
|
|
195
|
+
ol_match = re.match(r'^(\d+)\.\s+(.+)$', stripped)
|
|
196
|
+
if ol_match:
|
|
197
|
+
close_blockquote()
|
|
198
|
+
if in_ul:
|
|
199
|
+
html_parts.append('</ul>')
|
|
200
|
+
in_ul = False
|
|
201
|
+
if not in_list:
|
|
202
|
+
html_parts.append('<ol>')
|
|
203
|
+
in_list = True
|
|
204
|
+
text = ol_match.group(2)
|
|
205
|
+
text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text)
|
|
206
|
+
text = re.sub(r'\[(.+?)\]\((.+?)\)', r'<a href="\2">\1</a>', text)
|
|
207
|
+
# 复选框
|
|
208
|
+
text = re.sub(r'^\[ \]\s*', '<input type="checkbox" disabled> ', text)
|
|
209
|
+
text = re.sub(r'^\[x\]\s*', '<input type="checkbox" checked disabled> ', text, flags=re.IGNORECASE)
|
|
210
|
+
html_parts.append(f'<li>{text}</li>')
|
|
211
|
+
continue
|
|
212
|
+
|
|
213
|
+
# 无序列表
|
|
214
|
+
ul_match = re.match(r'^[-*•]\s+(.+)$', stripped)
|
|
215
|
+
if ul_match:
|
|
216
|
+
close_blockquote()
|
|
217
|
+
if in_list:
|
|
218
|
+
html_parts.append('</ol>')
|
|
219
|
+
in_list = False
|
|
220
|
+
if not in_ul:
|
|
221
|
+
html_parts.append('<ul>')
|
|
222
|
+
in_ul = True
|
|
223
|
+
text = ul_match.group(1)
|
|
224
|
+
text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text)
|
|
225
|
+
text = re.sub(r'\[(.+?)\]\((.+?)\)', r'<a href="\2">\1</a>', text)
|
|
226
|
+
text = re.sub(r'^\[ \]\s*', '<input type="checkbox" disabled> ', text)
|
|
227
|
+
text = re.sub(r'^\[x\]\s*', '<input type="checkbox" checked disabled> ', text, flags=re.IGNORECASE)
|
|
228
|
+
html_parts.append(f'<li>{text}</li>')
|
|
229
|
+
continue
|
|
230
|
+
|
|
231
|
+
# 普通段落
|
|
232
|
+
close_lists()
|
|
233
|
+
close_blockquote()
|
|
234
|
+
text = stripped
|
|
235
|
+
text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text)
|
|
236
|
+
text = re.sub(r'\[(.+?)\]\((.+?)\)', r'<a href="\2">\1</a>', text)
|
|
237
|
+
html_parts.append(f'<p>{text}</p>')
|
|
238
|
+
|
|
239
|
+
close_lists()
|
|
240
|
+
close_blockquote()
|
|
241
|
+
return '\n'.join(html_parts)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def generate_action_html(date_str: str, md_path: str, out_path: str):
|
|
245
|
+
"""生成行动建议 HTML 页面,风格与 index.html 一致。"""
|
|
246
|
+
with open(md_path, 'r', encoding='utf-8') as f:
|
|
247
|
+
md_content = f.read()
|
|
248
|
+
content_html = simple_markdown_to_html(md_content)
|
|
249
|
+
|
|
250
|
+
html = f'''<!DOCTYPE html>
|
|
251
|
+
<html lang="zh-CN">
|
|
252
|
+
<head>
|
|
253
|
+
<meta charset="UTF-8">
|
|
254
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
255
|
+
<title>行动建议 — {date_str}</title>
|
|
256
|
+
<style>
|
|
257
|
+
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
|
258
|
+
body {{
|
|
259
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
260
|
+
background: var(--bg); color: var(--text); line-height: 1.75;
|
|
261
|
+
min-height: 100vh; -webkit-font-smoothing: antialiased;
|
|
262
|
+
}}
|
|
263
|
+
.page {{ max-width: 780px; margin: 0 auto; padding: 32px 24px 60px; }}
|
|
264
|
+
|
|
265
|
+
/* ---- Header ---- */
|
|
266
|
+
.header {{ text-align: center; margin-bottom: 36px; padding-top: 12px; position: relative; }}
|
|
267
|
+
.header::after {{
|
|
268
|
+
content: ''; position: absolute; bottom: -12px; left: 50%; transform: translateX(-50%);
|
|
269
|
+
width: 50px; height: 2px; background: linear-gradient(90deg, #8b6914, #c8963e); border-radius: 2px;
|
|
270
|
+
}}
|
|
271
|
+
.header h1 {{
|
|
272
|
+
font-size: 30px; font-weight: 800; color: var(--text-title);
|
|
273
|
+
display: inline-flex; align-items: center; gap: 10px;
|
|
274
|
+
}}
|
|
275
|
+
.back-link {{
|
|
276
|
+
display: inline-block; margin-top: 16px;
|
|
277
|
+
font-size: 18px; color: var(--text-muted); text-decoration: none;
|
|
278
|
+
padding: 7px 18px; border-radius: 20px;
|
|
279
|
+
background: var(--bg-soft); border: 1px solid #d4c5a060;
|
|
280
|
+
transition: all .25s;
|
|
281
|
+
}}
|
|
282
|
+
.back-link:hover {{ color: var(--accent); border-color: #c8963e50; background: var(--bg-soft); }}
|
|
283
|
+
|
|
284
|
+
/* ---- Content ---- */
|
|
285
|
+
.content {{
|
|
286
|
+
background: var(--bg-card); border-radius: 14px; padding: 32px 36px;
|
|
287
|
+
border: 1px solid #d4c5a0; margin-top: 8px;
|
|
288
|
+
}}
|
|
289
|
+
.content h1 {{ font-size: 26px; font-weight: 700; color: var(--text-title); margin: 28px 0 14px; padding-bottom: 8px; border-bottom: 1px solid #d4c5a0; }}
|
|
290
|
+
.content h1:first-child {{ margin-top: 0; }}
|
|
291
|
+
.content h2 {{ font-size: 23px; font-weight: 700; color: #5a4a2a; margin: 24px 0 12px; }}
|
|
292
|
+
.content h3 {{ font-size: 21px; font-weight: 600; color: #6b5535; margin: 20px 0 10px; }}
|
|
293
|
+
.content h4 {{ font-size: 19px; font-weight: 600; color: #7a6545; margin: 16px 0 8px; }}
|
|
294
|
+
.content p {{ margin: 10px 0; font-size: 19px; color: #4a4035; }}
|
|
295
|
+
.content strong {{ color: var(--text-title); font-weight: 700; }}
|
|
296
|
+
.content a {{ color: var(--accent); text-decoration: none; border-bottom: 1px solid #c8963e40; transition: all .2s; }}
|
|
297
|
+
.content a:hover {{ border-bottom-color: #c8963e; }}
|
|
298
|
+
.content ul, .content ol {{ margin: 12px 0; padding-left: 28px; }}
|
|
299
|
+
.content li {{ margin: 8px 0; font-size: 19px; color: #4a4035; }}
|
|
300
|
+
.content li input[type="checkbox"] {{ margin-right: 8px; accent-color: var(--accent); }}
|
|
301
|
+
.content blockquote {{
|
|
302
|
+
border-left: 3px solid #c8963e; padding: 10px 18px; margin: 16px 0;
|
|
303
|
+
background: var(--bg-soft); border-radius: 0 10px 10px 0; color: #6b5535;
|
|
304
|
+
}}
|
|
305
|
+
.content blockquote p {{ margin: 4px 0; color: #6b5535; }}
|
|
306
|
+
.content hr {{ border: none; border-top: 1px solid #d4c5a0; margin: 28px 0; }}
|
|
307
|
+
.content code {{ background: #f0e8d5; padding: 2px 7px; border-radius: 5px; font-size: 18px; color: var(--accent); }}
|
|
308
|
+
|
|
309
|
+
@media (max-width: 640px) {{
|
|
310
|
+
.page {{ padding: 20px 14px 40px; }}
|
|
311
|
+
.content {{ padding: 20px 18px; border-radius: 10px; }}
|
|
312
|
+
.header h1 {{ font-size: 26px; }}
|
|
313
|
+
}}
|
|
314
|
+
</style>
|
|
315
|
+
</head>
|
|
316
|
+
<body>
|
|
317
|
+
<div class="page">
|
|
318
|
+
<div class="header">
|
|
319
|
+
<h1>📋 行动建议 — {date_str}</h1>
|
|
320
|
+
<div><a href="./index.html" class="back-link">← 返回日报</a></div>
|
|
321
|
+
</div>
|
|
322
|
+
<div class="content">
|
|
323
|
+
{content_html}
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
</body>
|
|
327
|
+
</html>'''
|
|
328
|
+
with open(out_path, 'w', encoding='utf-8') as f:
|
|
329
|
+
f.write(html)
|
|
330
|
+
|
|
331
|
+
|
|
109
332
|
def generate_article_viewer(out_path: str):
|
|
110
333
|
"""生成 article.html — Markdown 渲染阅读器。"""
|
|
111
334
|
html = r'''<!DOCTYPE html>
|
|
@@ -147,100 +370,95 @@ function md5(string) {
|
|
|
147
370
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
148
371
|
body {
|
|
149
372
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
150
|
-
background:
|
|
151
|
-
color:
|
|
373
|
+
background: var(--bg);
|
|
374
|
+
color: var(--text);
|
|
152
375
|
min-height: 100vh;
|
|
153
376
|
line-height: 1.8;
|
|
377
|
+
-webkit-font-smoothing: antialiased;
|
|
154
378
|
}
|
|
155
|
-
.container { max-width:
|
|
379
|
+
.container { max-width: 1440px; margin: 0 auto; padding: 24px 32px; display: flex; gap: 28px; }
|
|
380
|
+
.article-col { flex: 1; min-width: 0; }
|
|
156
381
|
.top-bar {
|
|
157
382
|
display: flex; align-items: center; gap: 12px; padding: 12px 0;
|
|
158
|
-
margin-bottom: 24px; border-bottom: 1px solid #
|
|
159
|
-
position: sticky; top: 0; background:
|
|
383
|
+
margin-bottom: 24px; border-bottom: 1px solid #d4c5a0;
|
|
384
|
+
position: sticky; top: 0; background: var(--bg); z-index: 10; flex-wrap: wrap;
|
|
160
385
|
}
|
|
161
386
|
.btn-back {
|
|
162
|
-
color:
|
|
163
|
-
padding: 6px 14px; border: 1px solid #
|
|
164
|
-
transition: all .2s; white-space: nowrap;
|
|
387
|
+
color: var(--text-muted); text-decoration: none; font-size: 18px;
|
|
388
|
+
padding: 6px 14px; border: 1px solid #d4c5a0; border-radius: 8px;
|
|
389
|
+
transition: all .2s; white-space: nowrap; background: var(--bg-soft);
|
|
165
390
|
}
|
|
166
|
-
.btn-back:hover { color:
|
|
167
|
-
.article-source { color:
|
|
168
|
-
.article-
|
|
169
|
-
.
|
|
170
|
-
.
|
|
171
|
-
.
|
|
172
|
-
.view-switch a { padding: 4px 12px; font-size: 12px; color: #64748b; text-decoration: none; transition: all .2s; }
|
|
173
|
-
.view-switch a.active { background: #334155; color: #e2e8f0; }
|
|
174
|
-
.view-switch a:hover:not(.active) { color: #94a3b8; }
|
|
175
|
-
.article-header { margin-bottom: 32px; padding-bottom: 20px; border-bottom: 1px solid #1e293b; }
|
|
176
|
-
.article-header h1 { font-size: 26px; font-weight: 700; line-height: 1.4; margin-bottom: 12px; color: #f1f5f9; }
|
|
177
|
-
.article-header .meta-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 13px; color: #64748b; }
|
|
178
|
-
.article-header .source-tag { background: #312e81; color: #a5b4fc; padding: 2px 10px; border-radius: 6px; font-size: 12px; }
|
|
391
|
+
.btn-back:hover { color: var(--text-title); border-color: #c8963e; background: var(--bg-card); }
|
|
392
|
+
.article-source { color: var(--accent); font-size: 17px; font-weight: 600; margin-left: auto; background: #c8963e10; padding: 2px 10px; border-radius: 6px; }
|
|
393
|
+
.article-header { margin-bottom: 32px; padding-bottom: 20px; border-bottom: 1px solid #d4c5a0; }
|
|
394
|
+
.article-header h1 { font-size: 30px; font-weight: 700; line-height: 1.4; margin-bottom: 12px; color: var(--text-title); }
|
|
395
|
+
.article-header .meta-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 17px; color: #96876a; }
|
|
396
|
+
.article-header .source-tag { background: #c8963e15; color: var(--accent); padding: 2px 10px; border-radius: 6px; font-size: 16px; }
|
|
179
397
|
.tags-row { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
|
|
180
|
-
.tag { font-size:
|
|
181
|
-
.article-body { font-size:
|
|
182
|
-
.article-body h2 { font-size:
|
|
183
|
-
.article-body h3 { font-size:
|
|
184
|
-
.article-body h4 { font-size:
|
|
398
|
+
.tag { font-size: 15px; padding: 2px 8px; background: #f0e8d5; border-radius: 6px; color: var(--text-muted); }
|
|
399
|
+
.article-body { font-size: 20px; color: #4a4035; }
|
|
400
|
+
.article-body h2 { font-size: 26px; font-weight: 700; margin: 32px 0 16px; color: var(--text-title); border-bottom: 1px solid #d4c5a0; padding-bottom: 8px; }
|
|
401
|
+
.article-body h3 { font-size: 22px; font-weight: 600; margin: 24px 0 12px; color: #5a4a2a; }
|
|
402
|
+
.article-body h4 { font-size: 20px; font-weight: 600; margin: 20px 0 8px; color: #6b5535; }
|
|
185
403
|
.article-body p { margin: 12px 0; }
|
|
186
|
-
.article-body strong { color:
|
|
187
|
-
.article-body a { color:
|
|
188
|
-
.article-body a:hover {
|
|
189
|
-
.article-body img { max-width: 100%; height: auto; border-radius: 8px; margin: 16px 0; border: 1px solid #
|
|
190
|
-
.article-body blockquote { border-left: 3px solid #
|
|
404
|
+
.article-body strong { color: var(--text-title); font-weight: 700; }
|
|
405
|
+
.article-body a { color: var(--accent); text-decoration: none; border-bottom: 1px solid #c8963e40; }
|
|
406
|
+
.article-body a:hover { border-bottom-color: #c8963e; }
|
|
407
|
+
.article-body img { max-width: 100%; height: auto; border-radius: 8px; margin: 16px 0; border: 1px solid #e0d5be; }
|
|
408
|
+
.article-body blockquote { border-left: 3px solid #c8963e; padding: 8px 16px; margin: 16px 0; background: var(--bg-soft); border-radius: 0 8px 8px 0; color: #6b5535; }
|
|
191
409
|
.article-body blockquote p { margin: 4px 0; }
|
|
192
|
-
.article-body code { background: #
|
|
193
|
-
.article-body pre { background:
|
|
194
|
-
.article-body pre code { background: none; padding: 0; color: #
|
|
410
|
+
.article-body code { background: #f0e8d5; padding: 2px 6px; border-radius: 4px; font-size: 18px; color: var(--accent); }
|
|
411
|
+
.article-body pre { background: var(--bg-soft); border: 1px solid #d4c5a0; border-radius: 10px; padding: 16px; overflow-x: auto; margin: 16px 0; font-size: 17px; line-height: 1.7; }
|
|
412
|
+
.article-body pre code { background: none; padding: 0; color: #4a4035; }
|
|
195
413
|
.article-body ul, .article-body ol { margin: 12px 0; padding-left: 24px; }
|
|
196
|
-
.article-body li { margin: 6px 0; color: #
|
|
197
|
-
.article-body hr { border: none; border-top: 1px solid #
|
|
198
|
-
.article-body table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size:
|
|
199
|
-
.article-body th { background: #
|
|
200
|
-
.article-body td { padding: 8px 14px; border-bottom: 1px solid #
|
|
201
|
-
.loading { text-align: center; padding: 80px 20px; color: #
|
|
202
|
-
.error-box { background: #
|
|
203
|
-
@media (max-width:
|
|
204
|
-
.container { padding:
|
|
205
|
-
.article-header h1 { font-size:
|
|
206
|
-
.article-body { font-size:
|
|
414
|
+
.article-body li { margin: 6px 0; color: #4a4035; }
|
|
415
|
+
.article-body hr { border: none; border-top: 1px solid #d4c5a0; margin: 24px 0; }
|
|
416
|
+
.article-body table { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 18px; }
|
|
417
|
+
.article-body th { background: #f0e8d5; padding: 10px 14px; text-align: left; color: var(--text-title); font-weight: 600; border-bottom: 2px solid #d4c5a0; }
|
|
418
|
+
.article-body td { padding: 8px 14px; border-bottom: 1px solid #e0d5be; color: #5c4a32; }
|
|
419
|
+
.loading { text-align: center; padding: 80px 20px; color: #96876a; font-size: 20px; }
|
|
420
|
+
.error-box { background: #dc5b5b10; border: 1px solid #dc5b5b30; border-radius: 12px; padding: 20px; text-align: center; color: #b04444; margin: 40px 0; }
|
|
421
|
+
@media (max-width: 768px) {
|
|
422
|
+
.container { padding: 16px 14px; flex-direction: column; }
|
|
423
|
+
.article-header h1 { font-size: 24px; }
|
|
424
|
+
.article-body { font-size: 19px; }
|
|
207
425
|
}
|
|
208
426
|
/* ---- Highlight & Notes ---- */
|
|
209
|
-
.article-body mark.hl { background: #fef08a; color:
|
|
427
|
+
.article-body mark.hl { background: #fef08a; color: var(--text-title); padding: 1px 0; border-radius: 2px; cursor: pointer; transition: all .2s; }
|
|
210
428
|
.article-body mark.hl:hover { background: #fde047; }
|
|
211
429
|
.article-body mark.hl.active { background: #fbbf24; box-shadow: 0 0 0 2px #fbbf2440; }
|
|
212
|
-
.selection-popup { position: absolute; z-index: 1000; background:
|
|
213
|
-
.selection-popup button { background: #
|
|
214
|
-
.selection-popup button:hover { background: #
|
|
215
|
-
.note-modal-overlay { position: fixed; inset: 0; background: #
|
|
216
|
-
.note-modal { background:
|
|
217
|
-
.note-modal h3 { color:
|
|
218
|
-
.note-modal .hl-text { background:
|
|
219
|
-
.note-modal textarea { width: 100%; height: 80px; background:
|
|
220
|
-
.note-modal textarea:focus { border-color: #
|
|
430
|
+
.selection-popup { position: absolute; z-index: 1000; background: var(--bg-card); border: 1px solid #c8963e; border-radius: 10px; padding: 6px 10px; display: none; box-shadow: 0 8px 24px #3c3a3820; }
|
|
431
|
+
.selection-popup button { background: #8b6914; color: #fff; border: none; padding: 6px 14px; border-radius: 7px; cursor: pointer; font-size: 17px; font-weight: 600; white-space: nowrap; }
|
|
432
|
+
.selection-popup button:hover { background: #6b4c1e; }
|
|
433
|
+
.note-modal-overlay { position: fixed; inset: 0; background: #3c3a3840; z-index: 2000; display: flex; align-items: center; justify-content: center; }
|
|
434
|
+
.note-modal { background: var(--bg-card); border: 1px solid #d4c5a0; border-radius: 14px; padding: 24px; width: 90%; max-width: 480px; box-shadow: 0 16px 48px #3c3a3830; }
|
|
435
|
+
.note-modal h3 { color: var(--text-title); font-size: 20px; margin-bottom: 12px; }
|
|
436
|
+
.note-modal .hl-text { background: var(--bg-soft); border: 1px solid #d4c5a0; border-radius: 8px; padding: 10px 14px; color: #6b5535; font-size: 17px; margin-bottom: 14px; max-height: 100px; overflow-y: auto; line-height: 1.6; }
|
|
437
|
+
.note-modal textarea { width: 100%; height: 80px; background: var(--bg-soft); border: 1px solid #d4c5a0; border-radius: 8px; color: var(--text); padding: 10px 14px; font-size: 17px; resize: vertical; font-family: inherit; outline: none; }
|
|
438
|
+
.note-modal textarea:focus { border-color: #c8963e; }
|
|
221
439
|
.note-modal .btn-row { display: flex; gap: 8px; justify-content: flex-end; margin-top: 14px; }
|
|
222
|
-
.note-modal .btn-row button { padding: 7px 18px; border-radius: 8px; font-size:
|
|
223
|
-
.note-modal .btn-save { background: #
|
|
224
|
-
.note-modal .btn-save:hover { background: #
|
|
225
|
-
.note-modal .btn-cancel { background: #
|
|
226
|
-
.note-modal .btn-cancel:hover { background: #
|
|
227
|
-
.notes-sidebar { position: fixed; right: 0; top: 0; width: 340px; height: 100vh; background:
|
|
440
|
+
.note-modal .btn-row button { padding: 7px 18px; border-radius: 8px; font-size: 17px; font-weight: 600; cursor: pointer; border: none; }
|
|
441
|
+
.note-modal .btn-save { background: #8b6914; color: #fff; }
|
|
442
|
+
.note-modal .btn-save:hover { background: #6b4c1e; }
|
|
443
|
+
.note-modal .btn-cancel { background: #f0e8d5; color: var(--text-muted); }
|
|
444
|
+
.note-modal .btn-cancel:hover { background: #e8dcc0; }
|
|
445
|
+
.notes-sidebar { position: fixed; right: 0; top: 0; width: 340px; height: 100vh; background: var(--bg-card); border-left: 1px solid #d4c5a0; z-index: 500; overflow-y: auto; transform: translateX(100%); transition: transform .3s ease; }
|
|
228
446
|
.notes-sidebar.open { transform: translateX(0); }
|
|
229
|
-
.notes-sidebar-header { padding: 16px 20px; border-bottom: 1px solid #
|
|
230
|
-
.notes-sidebar-header h3 { color:
|
|
231
|
-
.notes-sidebar-header button { background: none; border: none; color: #
|
|
232
|
-
.notes-sidebar-header button:hover { color:
|
|
233
|
-
.note-item { padding: 14px 20px; border-bottom: 1px solid #
|
|
234
|
-
.note-item .hl-text { color:
|
|
235
|
-
.note-item .hl-note { color: #
|
|
236
|
-
.note-item .hl-meta { color: #
|
|
237
|
-
.note-item .hl-meta button { background: none; border: none; color: #dc5b5b; font-size:
|
|
238
|
-
.note-item .hl-meta button:hover { color: #
|
|
239
|
-
.notes-sidebar .empty { text-align: center; padding: 60px 20px; color: #
|
|
240
|
-
.btn-notes-toggle { position: fixed; right: 16px; bottom: 24px; z-index: 501; background: #
|
|
241
|
-
.btn-notes-toggle:hover { background: #
|
|
242
|
-
.btn-notes-toggle .badge { position: absolute; top: -4px; right: -4px; background: #ef4444; color: #fff; font-size:
|
|
243
|
-
@media (max-width:
|
|
447
|
+
.notes-sidebar-header { padding: 16px 20px; border-bottom: 1px solid #d4c5a0; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; background: var(--bg-card); z-index: 2; }
|
|
448
|
+
.notes-sidebar-header h3 { color: var(--text-title); font-size: 19px; font-weight: 700; }
|
|
449
|
+
.notes-sidebar-header button { background: none; border: none; color: #96876a; font-size: 22px; cursor: pointer; }
|
|
450
|
+
.notes-sidebar-header button:hover { color: var(--text-title); }
|
|
451
|
+
.note-item { padding: 14px 20px; border-bottom: 1px solid #e0d5be; }
|
|
452
|
+
.note-item .hl-text { color: var(--accent); font-size: 17px; line-height: 1.6; margin-bottom: 8px; border-left: 3px solid #c8963e; padding-left: 10px; }
|
|
453
|
+
.note-item .hl-note { color: #4a4035; font-size: 17px; line-height: 1.6; margin-bottom: 6px; }
|
|
454
|
+
.note-item .hl-meta { color: #b8a080; font-size: 15px; display: flex; align-items: center; gap: 10px; }
|
|
455
|
+
.note-item .hl-meta button { background: none; border: none; color: #dc5b5b; font-size: 15px; cursor: pointer; }
|
|
456
|
+
.note-item .hl-meta button:hover { color: #b04444; }
|
|
457
|
+
.notes-sidebar .empty { text-align: center; padding: 60px 20px; color: #b8a080; font-size: 18px; line-height: 2; }
|
|
458
|
+
.btn-notes-toggle { position: fixed; right: 16px; bottom: 24px; z-index: 501; background: #8b6914; color: #fff; border: none; width: 44px; height: 44px; border-radius: 12px; font-size: 22px; cursor: pointer; box-shadow: 0 4px 16px #8b691430; transition: all .2s; display: flex; align-items: center; justify-content: center; }
|
|
459
|
+
.btn-notes-toggle:hover { background: #6b4c1e; transform: scale(1.05); }
|
|
460
|
+
.btn-notes-toggle .badge { position: absolute; top: -4px; right: -4px; background: #ef4444; color: #fff; font-size: 14px; width: 18px; height: 18px; border-radius: 9px; display: flex; align-items: center; justify-content: center; font-weight: 700; }
|
|
461
|
+
@media (max-width: 768px) { .notes-sidebar { width: 100%; } }
|
|
244
462
|
</style>
|
|
245
463
|
</head>
|
|
246
464
|
<body>
|
|
@@ -317,14 +535,8 @@ body {
|
|
|
317
535
|
const tagsHtml = tags.length ? '<div class="tags-row">' + tags.map(t => '<span class="tag">' + t + '</span>').join('') + '</div>' : '';
|
|
318
536
|
document.getElementById('app').innerHTML =
|
|
319
537
|
'<div class="top-bar">' +
|
|
320
|
-
'<a href="./" class="btn-back">\u2190 \u8fd4\u56de\
|
|
538
|
+
'<a href="./index.html" class="btn-back">\u2190 \u8fd4\u56de\u65e5\u62a5</a>' +
|
|
321
539
|
'<span class="article-source">' + (source || '') + '</span>' +
|
|
322
|
-
'<span class="article-meta">\u9605\u8bfb\u6a21\u5f0f</span>' +
|
|
323
|
-
'<span class="mode-badge html">\ud83c\udfa8 HTML</span>' +
|
|
324
|
-
'<div class="view-switch">' +
|
|
325
|
-
'<a href="' + file + '">\ud83d\udcdd MD</a>' +
|
|
326
|
-
'<a href="article.html?file=' + encodeURIComponent(file) + '" class="active">\ud83c\udfa8 HTML</a>' +
|
|
327
|
-
'</div>' +
|
|
328
540
|
'</div>' +
|
|
329
541
|
'<div class="article-header">' +
|
|
330
542
|
'<h1>' + title + '</h1>' +
|
|
@@ -332,7 +544,7 @@ body {
|
|
|
332
544
|
tagsHtml +
|
|
333
545
|
'</div>' +
|
|
334
546
|
'<div class="article-body">' + htmlContent + '</div>' +
|
|
335
|
-
'<div style="text-align:center;padding:40px;color:#475569;font-size:
|
|
547
|
+
'<div style="text-align:center;padding:40px;color:#475569;font-size: 16px;">\u2014 END \u2014</div>' +
|
|
336
548
|
'<div class="notes-sidebar" id="notes-sidebar">' +
|
|
337
549
|
'<div class="notes-sidebar-header"><h3>\ud83d\udcdd \u7b14\u8bb0</h3><button onclick="toggleNotes()">\u2715</button></div>' +
|
|
338
550
|
'<div id="notes-list"><div class="empty">\u9009\u4e2d\u6587\u5b57\u540e\u70b9\u51fb\u201c\u6807\u8bb0\u7b14\u8bb0\u201d\u5373\u53ef\u6dfb\u52a0</div></div>' +
|
|
@@ -340,7 +552,7 @@ body {
|
|
|
340
552
|
'<button class="btn-notes-toggle" id="btn-notes-toggle" onclick="toggleNotes()" title="\u7b14\u8bb0">\ud83d\udcdd<span class="badge" id="notes-badge" style="display:none">0</span></button>';
|
|
341
553
|
} catch(e) {
|
|
342
554
|
document.getElementById('app').innerHTML =
|
|
343
|
-
'<div class="error-box">\u274c \u52a0\u8f7d\u5931\u8d25: ' + e.message + '<br><br><a href="./" class="btn-back">\u2190 \u8fd4\u56de\
|
|
555
|
+
'<div class="error-box">\u274c \u52a0\u8f7d\u5931\u8d25: ' + e.message + '<br><br><a href="./index.html" class="btn-back">\u2190 \u8fd4\u56de\u65e5\u62a5</a></div>';
|
|
344
556
|
}
|
|
345
557
|
})();
|
|
346
558
|
|
|
@@ -608,7 +820,7 @@ def generate_html(date_str: str, topics: dict, action_suggestions_exist: bool, b
|
|
|
608
820
|
relevance_badge = '<span class="relevance mid">中相关</span>'
|
|
609
821
|
|
|
610
822
|
cards.append(f'''
|
|
611
|
-
<article class="card" data-id="{escape_html(art['rel_path'])}" data-topic="{topic}">
|
|
823
|
+
<article class="card" data-id="{escape_html(art['rel_path'])}" data-topic="{topic}" onclick="if(!event.target.closest('button,a')){{var a=this.querySelector('a[href]');if(a)a.click()}}" style="cursor:pointer">
|
|
612
824
|
<div class="card-header">
|
|
613
825
|
<div class="card-title-row">
|
|
614
826
|
<span class="unread-dot" id="dot-{escape_html(art['rel_path']).replace('/', '_').replace('.', '_')}"></span>
|
|
@@ -629,13 +841,13 @@ def generate_html(date_str: str, topics: dict, action_suggestions_exist: bool, b
|
|
|
629
841
|
<div class="card-actions">
|
|
630
842
|
<a href="{escape_html(art['rel_path'])}"
|
|
631
843
|
class="btn-read" onclick="markRead('{escape_html(art['rel_path'])}')">
|
|
632
|
-
📖
|
|
844
|
+
📖 阅读全文 <span class="arrow">→</span>
|
|
633
845
|
</a>
|
|
634
|
-
<button class="btn-toggle" onclick="toggleRead('{escape_html(art['rel_path'])}')">
|
|
635
|
-
<span class="toggle-label"
|
|
846
|
+
<button class="btn-toggle" onclick="event.stopPropagation();toggleRead('{escape_html(art['rel_path'])}')">
|
|
847
|
+
<span class="toggle-label"></span>
|
|
636
848
|
</button>
|
|
637
|
-
<button class="btn-fav" onclick="toggleFav('{escape_html(art['rel_path'])}')" title="收藏">
|
|
638
|
-
|
|
849
|
+
<button class="btn-fav" onclick="event.stopPropagation();toggleFav('{escape_html(art['rel_path'])}')" title="收藏">
|
|
850
|
+
<span class="fav-label"></span>
|
|
639
851
|
</button>
|
|
640
852
|
</div>
|
|
641
853
|
</article>''')
|
|
@@ -654,7 +866,7 @@ def generate_html(date_str: str, topics: dict, action_suggestions_exist: bool, b
|
|
|
654
866
|
action_link = ''
|
|
655
867
|
if action_suggestions_exist:
|
|
656
868
|
action_link = '''
|
|
657
|
-
<a href="./行动建议.
|
|
869
|
+
<a href="./行动建议.html" class="action-link">📋 行动建议</a>
|
|
658
870
|
'''
|
|
659
871
|
|
|
660
872
|
briefing_html = ''
|
|
@@ -673,19 +885,49 @@ def generate_html(date_str: str, topics: dict, action_suggestions_exist: bool, b
|
|
|
673
885
|
<title>公众号日报 — {date_str}</title>
|
|
674
886
|
<style>
|
|
675
887
|
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
|
888
|
+
:root {{
|
|
889
|
+
--bg: #f5f0e8;
|
|
890
|
+
--bg-card: #fefcf8;
|
|
891
|
+
--bg-soft: #faf6ef;
|
|
892
|
+
--bg-hover: #fffdf7;
|
|
893
|
+
--border: #e0d5be;
|
|
894
|
+
--border-soft: #d4c5a060;
|
|
895
|
+
--accent: #8b6914;
|
|
896
|
+
--accent-light: #c8963e;
|
|
897
|
+
--accent-glow: #c8963e15;
|
|
898
|
+
--text: #3c3a38;
|
|
899
|
+
--text-title: #2a2018;
|
|
900
|
+
--text-muted: #96876a;
|
|
901
|
+
--text-faint: #b8a080;
|
|
902
|
+
--radius: 14px;
|
|
903
|
+
--radius-sm: 10px;
|
|
904
|
+
--shadow-sm: 0 1px 3px #00000008;
|
|
905
|
+
--shadow-md: 0 4px 16px #3c3a380a;
|
|
906
|
+
--shadow-lg: 0 8px 28px #3c3a3810;
|
|
907
|
+
--transition: .25s cubic-bezier(.4,0,.2,1);
|
|
908
|
+
}}
|
|
909
|
+
|
|
910
|
+
[data-theme="dark"] {{
|
|
911
|
+
--bg: #1a1816; --bg-card: #252220; --bg-soft: #2a2624;
|
|
912
|
+
--bg-hover: #2f2b28; --border: #3d3835; --border-soft: #332e2b;
|
|
913
|
+
--accent: #c8963e; --accent-light: #d4a853; --accent-glow: #c8963e20;
|
|
914
|
+
--text: #d4cfc8; --text-title: #e8e0d4; --text-muted: #968b7a;
|
|
915
|
+
--text-faint: #6b6358;
|
|
916
|
+
}}
|
|
676
917
|
body {{
|
|
677
918
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
678
|
-
background:
|
|
679
|
-
|
|
919
|
+
background: var(--bg);
|
|
920
|
+
background-image: radial-gradient(circle at 20% 0%, #faf6ef 0%, transparent 50%), radial-gradient(circle at 80% 100%, #f0e8d530 0%, transparent 50%);
|
|
921
|
+
color: var(--text);
|
|
680
922
|
min-height: 100vh;
|
|
681
923
|
-webkit-font-smoothing: antialiased;
|
|
682
924
|
}}
|
|
683
|
-
.container {{ max-width:
|
|
925
|
+
.container {{ max-width: 1300px; margin: 0 auto; padding: 28px 28px 48px; }}
|
|
684
926
|
|
|
685
927
|
/* ---- Header ---- */
|
|
686
928
|
.header {{
|
|
687
929
|
text-align: center;
|
|
688
|
-
padding:
|
|
930
|
+
padding: 40px 20px 36px;
|
|
689
931
|
margin-bottom: 24px;
|
|
690
932
|
position: relative;
|
|
691
933
|
}}
|
|
@@ -693,205 +935,258 @@ body {{
|
|
|
693
935
|
content: '';
|
|
694
936
|
position: absolute;
|
|
695
937
|
bottom: 0; left: 50%; transform: translateX(-50%);
|
|
696
|
-
width:
|
|
697
|
-
background: linear-gradient(90deg,
|
|
938
|
+
width: 56px; height: 3px;
|
|
939
|
+
background: linear-gradient(90deg, var(--accent), var(--accent-light), #d4a853);
|
|
698
940
|
border-radius: 2px;
|
|
699
941
|
}}
|
|
700
942
|
.header h1 {{
|
|
701
|
-
font-size:
|
|
702
|
-
color:
|
|
943
|
+
font-size: 32px; font-weight: 800; letter-spacing: -.5px;
|
|
944
|
+
color: var(--text-title);
|
|
945
|
+
background: linear-gradient(135deg, #4a3728, #6b5535);
|
|
946
|
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
|
947
|
+
background-clip: text;
|
|
703
948
|
}}
|
|
704
949
|
.header .meta {{
|
|
705
|
-
margin-top:
|
|
706
|
-
display: flex; align-items: center; justify-content: center; gap:
|
|
950
|
+
margin-top: 14px; color: var(--text-muted); font-size: 18px;
|
|
951
|
+
display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap;
|
|
707
952
|
}}
|
|
708
953
|
.action-link {{
|
|
709
|
-
color:
|
|
710
|
-
padding:
|
|
711
|
-
font-size:
|
|
954
|
+
color: var(--accent); text-decoration: none; font-weight: 600;
|
|
955
|
+
padding: 7px 18px; border: 1px solid #c8963e40; border-radius: 22px;
|
|
956
|
+
font-size: 17px; transition: all var(--transition); background: var(--bg-soft);
|
|
957
|
+
display: inline-flex; align-items: center; gap: 5px;
|
|
958
|
+
box-shadow: var(--shadow-sm);
|
|
712
959
|
}}
|
|
713
|
-
.action-link:hover {{ background:
|
|
960
|
+
.action-link:hover {{ background: var(--accent-glow); border-color: var(--accent-light); transform: translateY(-2px); box-shadow: var(--shadow-md); }}
|
|
714
961
|
|
|
715
|
-
/* ----
|
|
716
|
-
.
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
background:
|
|
720
|
-
|
|
962
|
+
/* ---- Search ---- */
|
|
963
|
+
.search-box {{
|
|
964
|
+
width: 100%; max-width: 560px; margin: 0 auto 20px; display: block;
|
|
965
|
+
padding: 14px 22px; border-radius: var(--radius); border: 1px solid var(--border);
|
|
966
|
+
background: var(--bg-card); color: var(--text); font-size: 18px; outline: none;
|
|
967
|
+
transition: all var(--transition); font-family: inherit;
|
|
968
|
+
box-shadow: var(--shadow-sm);
|
|
721
969
|
}}
|
|
722
|
-
.
|
|
723
|
-
.
|
|
724
|
-
|
|
970
|
+
.search-box:focus {{ border-color: var(--accent-light); box-shadow: 0 0 0 4px var(--accent-glow), var(--shadow-md); }}
|
|
971
|
+
.search-box::placeholder {{ color: var(--text-faint); }}
|
|
972
|
+
|
|
973
|
+
/* ---- Toolbar: Stats + Tabs ---- */
|
|
974
|
+
.toolbar {{
|
|
975
|
+
display: flex; align-items: center; gap: 12px;
|
|
976
|
+
flex-wrap: wrap; margin-bottom: 28px;
|
|
977
|
+
}}
|
|
978
|
+
.stats-bar {{
|
|
979
|
+
display: flex; align-items: center; gap: 10px;
|
|
980
|
+
flex-wrap: wrap; padding: 8px 16px;
|
|
981
|
+
background: var(--bg-soft); border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
|
|
982
|
+
font-size: 17px; color: var(--text-muted);
|
|
983
|
+
box-shadow: var(--shadow-sm);
|
|
725
984
|
}}
|
|
985
|
+
.stats-bar strong {{ color: var(--text); font-weight: 700; }}
|
|
986
|
+
.stats-group {{ display: flex; align-items: center; gap: 8px; }}
|
|
987
|
+
.stats-divider {{ width: 1px; height: 18px; background: #d4c5a080; }}
|
|
726
988
|
.btn-clear {{
|
|
727
|
-
background: #f0e8d5; color: #5c4a32; border: 1px solid
|
|
728
|
-
padding: 5px
|
|
729
|
-
font-size:
|
|
989
|
+
background: #f0e8d5; color: #5c4a32; border: 1px solid var(--border);
|
|
990
|
+
padding: 5px 13px; border-radius: 8px; cursor: pointer;
|
|
991
|
+
font-size: 16px; font-weight: 500; transition: all var(--transition);
|
|
992
|
+
white-space: nowrap;
|
|
730
993
|
}}
|
|
731
|
-
.btn-clear:hover {{ background: #e8dcc0; border-color:
|
|
994
|
+
.btn-clear:hover {{ background: #e8dcc0; border-color: var(--accent-light); color: var(--text); transform: translateY(-1px); }}
|
|
732
995
|
.btn-clear.active-mode {{
|
|
733
|
-
background:
|
|
996
|
+
background: var(--accent) !important; border-color: var(--accent) !important; color: #fff !important;
|
|
734
997
|
}}
|
|
735
998
|
|
|
736
999
|
/* ---- Tabs ---- */
|
|
737
1000
|
.tabs {{
|
|
738
|
-
display: flex; gap:
|
|
739
|
-
|
|
740
|
-
background:
|
|
741
|
-
|
|
1001
|
+
display: flex; gap: 4px; flex-wrap: wrap; flex: 1; min-width: 280px;
|
|
1002
|
+
padding: 5px;
|
|
1003
|
+
background: var(--bg-soft); border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
|
|
1004
|
+
box-shadow: var(--shadow-sm);
|
|
742
1005
|
}}
|
|
743
1006
|
.tab-btn {{
|
|
744
|
-
background: transparent; color:
|
|
745
|
-
padding:
|
|
746
|
-
font-size:
|
|
1007
|
+
background: transparent; color: var(--text-muted); border: none;
|
|
1008
|
+
padding: 8px 16px; border-radius: 9px; cursor: pointer;
|
|
1009
|
+
font-size: 18px; font-weight: 500; transition: all var(--transition);
|
|
1010
|
+
white-space: nowrap;
|
|
747
1011
|
}}
|
|
748
|
-
.tab-btn:hover {{ color:
|
|
1012
|
+
.tab-btn:hover {{ color: var(--text); background: #f0e8d5; }}
|
|
749
1013
|
.tab-btn.active {{
|
|
750
|
-
color:
|
|
751
|
-
box-shadow: 0
|
|
1014
|
+
color: var(--text-title); background: #dcc89a;
|
|
1015
|
+
box-shadow: 0 2px 8px #00000015, inset 0 1px 0 #fff8;
|
|
752
1016
|
font-weight: 700;
|
|
753
|
-
border: 1px solid #c8963e30;
|
|
754
1017
|
}}
|
|
755
1018
|
.tab-btn .count {{
|
|
756
|
-
background: #d4c5a040; padding:
|
|
757
|
-
font-size:
|
|
1019
|
+
background: #d4c5a040; padding: 1px 7px; border-radius: 7px;
|
|
1020
|
+
font-size: 15px; margin-left: 4px; font-weight: 600;
|
|
758
1021
|
}}
|
|
759
|
-
.tab-btn.active .count {{ background:
|
|
1022
|
+
.tab-btn.active .count {{ background: var(--accent-glow); color: var(--accent); }}
|
|
760
1023
|
|
|
761
1024
|
/* ---- Topic Sections ---- */
|
|
762
1025
|
.topic-section {{ display: none; }}
|
|
763
|
-
.topic-section.active {{ display: block; }}
|
|
1026
|
+
.topic-section.active {{ display: block; animation: fadeIn .35s cubic-bezier(.4,0,.2,1); }}
|
|
1027
|
+
@keyframes fadeIn {{ from {{ opacity: 0; transform: translateY(8px); }} to {{ opacity: 1; transform: translateY(0); }} }}
|
|
764
1028
|
.topic-heading {{
|
|
765
|
-
font-size:
|
|
766
|
-
display: flex; align-items: center; gap: 10px; color:
|
|
1029
|
+
font-size: 22px; font-weight: 700; margin-bottom: 16px; margin-top: 4px;
|
|
1030
|
+
display: flex; align-items: center; gap: 10px; color: var(--text-title);
|
|
767
1031
|
}}
|
|
768
1032
|
.topic-heading::after {{
|
|
769
|
-
content: ''; flex: 1; height: 1px; background: #d4c5a0; margin-left:
|
|
1033
|
+
content: ''; flex: 1; height: 1px; background: linear-gradient(90deg, #d4c5a0, transparent); margin-left: 4px;
|
|
770
1034
|
}}
|
|
771
|
-
.topic-heading .count {{ font-size:
|
|
1035
|
+
.topic-heading .count {{ font-size: 16px; color: var(--text-muted); font-weight: 400; background: #f0e8d5; padding: 2px 10px; border-radius: 10px; }}
|
|
772
1036
|
|
|
773
1037
|
/* ---- Cards ---- */
|
|
774
1038
|
.cards-grid {{ display: flex; flex-direction: column; gap: 10px; }}
|
|
775
1039
|
.card {{
|
|
776
|
-
background:
|
|
777
|
-
border: 1px solid
|
|
1040
|
+
background: var(--bg-card); border-radius: var(--radius); padding: 20px 24px;
|
|
1041
|
+
border: 1px solid var(--border); transition: all var(--transition);
|
|
778
1042
|
position: relative; overflow: hidden;
|
|
779
1043
|
}}
|
|
780
1044
|
.card::before {{
|
|
781
1045
|
content: ''; position: absolute; top: 0; left: 0; width: 3px; height: 100%;
|
|
782
|
-
background: transparent; transition: background .3s; border-radius:
|
|
1046
|
+
background: transparent; transition: background .3s; border-radius: var(--radius) 0 0 var(--radius);
|
|
783
1047
|
}}
|
|
784
1048
|
.card:hover {{
|
|
785
|
-
border-color: #
|
|
786
|
-
transform: translateY(-
|
|
1049
|
+
border-color: #c8963e60; background: var(--bg-hover);
|
|
1050
|
+
transform: translateY(-2px); box-shadow: var(--shadow-lg);
|
|
787
1051
|
}}
|
|
788
|
-
.card:hover::before {{ background:
|
|
1052
|
+
.card:hover::before {{ background: var(--accent-light); }}
|
|
789
1053
|
.card.read {{
|
|
790
|
-
opacity: 0.
|
|
1054
|
+
opacity: 0.48; background: #f8f3ea; border-color: #d4c5a040;
|
|
791
1055
|
}}
|
|
792
|
-
|
|
1056
|
+
|
|
1057
|
+
|
|
793
1058
|
.card.read:hover::before {{ background: #b8a080; }}
|
|
794
|
-
.card.faved {{ border-color: #
|
|
1059
|
+
.card.faved {{ border-color: #c8963e50; background: #fffcf3; box-shadow: 0 2px 12px #c8963e0a; }}
|
|
795
1060
|
|
|
796
|
-
.card-header {{ margin-bottom:
|
|
1061
|
+
.card-header {{ margin-bottom: 6px; }}
|
|
797
1062
|
.card-title-row {{ display: flex; align-items: flex-start; gap: 10px; }}
|
|
798
1063
|
.unread-dot {{
|
|
799
|
-
width:
|
|
800
|
-
background:
|
|
801
|
-
box-shadow: 0 0
|
|
1064
|
+
width: 8px; height: 8px; border-radius: 50%;
|
|
1065
|
+
background: var(--accent-light); flex-shrink: 0; margin-top: 7px; transition: all .3s;
|
|
1066
|
+
box-shadow: 0 0 0 3px var(--accent-glow);
|
|
1067
|
+
animation: pulse 2s ease-in-out infinite;
|
|
802
1068
|
}}
|
|
803
|
-
|
|
1069
|
+
@keyframes pulse {{ 0%, 100% {{ box-shadow: 0 0 0 3px var(--accent-glow); }} 50% {{ box-shadow: 0 0 0 5px #c8963e10; }} }}
|
|
1070
|
+
.card.read .unread-dot {{ background: #c8b898; box-shadow: none; animation: none; }}
|
|
804
1071
|
.card-title {{
|
|
805
|
-
font-size:
|
|
1072
|
+
font-size: 20px; font-weight: 700; flex: 1; line-height: 1.5; letter-spacing: -.2px;
|
|
806
1073
|
}}
|
|
807
|
-
.card-title a {{ color:
|
|
808
|
-
.card-title a:hover {{ color:
|
|
809
|
-
.card.read .card-title a {{ color:
|
|
1074
|
+
.card-title a {{ color: var(--text-title); text-decoration: none; transition: color .2s; }}
|
|
1075
|
+
.card-title a:hover {{ color: var(--accent); }}
|
|
1076
|
+
.card.read .card-title a {{ color: var(--text-muted); }}
|
|
810
1077
|
|
|
811
|
-
.card-meta {{ display: flex; align-items: center; gap: 8px; margin-top:
|
|
1078
|
+
.card-meta {{ display: flex; align-items: center; gap: 8px; margin-top: 5px; font-size: 16px; color: var(--text-muted); font-weight: 500; }}
|
|
812
1079
|
.card-meta .source {{
|
|
813
|
-
color:
|
|
1080
|
+
color: var(--accent); font-weight: 600;
|
|
1081
|
+
background: var(--accent-glow); padding: 1px 8px; border-radius: 4px;
|
|
814
1082
|
}}
|
|
815
1083
|
|
|
816
1084
|
.relevance {{
|
|
817
|
-
font-size:
|
|
818
|
-
flex-shrink: 0; letter-spacing: .3px;
|
|
1085
|
+
font-size: 14px; padding: 2px 8px; border-radius: 10px; font-weight: 700;
|
|
1086
|
+
flex-shrink: 0; letter-spacing: .3px; text-transform: uppercase;
|
|
819
1087
|
}}
|
|
820
|
-
.relevance.high {{ background: #
|
|
821
|
-
.relevance.mid {{ background: #
|
|
1088
|
+
.relevance.high {{ background: #dc5b5b12; color: #b04444; border: 1px solid #dc5b5b25; }}
|
|
1089
|
+
.relevance.mid {{ background: #c8963e12; color: var(--accent); border: 1px solid #c8963e30; }}
|
|
822
1090
|
|
|
823
|
-
.card-tags {{ display: flex; gap:
|
|
1091
|
+
.card-tags {{ display: flex; gap: 5px; flex-wrap: wrap; margin: 8px 0 6px; }}
|
|
824
1092
|
.tag {{
|
|
825
|
-
font-size:
|
|
826
|
-
color:
|
|
1093
|
+
font-size: 15px; padding: 2px 9px; background: #f0e8d5; border-radius: 5px;
|
|
1094
|
+
color: var(--text-muted); font-weight: 500; letter-spacing: .1px;
|
|
1095
|
+
transition: all .15s;
|
|
827
1096
|
}}
|
|
1097
|
+
.tag:hover {{ background: #e8dcc0; color: var(--text); }}
|
|
828
1098
|
.tag-more {{
|
|
829
|
-
background: #
|
|
1099
|
+
background: #e8dcc0; color: var(--text-muted); font-weight: 600; cursor: default;
|
|
830
1100
|
}}
|
|
831
1101
|
|
|
832
1102
|
.card-preview {{
|
|
833
|
-
font-size:
|
|
1103
|
+
font-size: 17px; color: #a0987a; line-height: 1.7; margin-bottom: 12px;
|
|
834
1104
|
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
|
835
1105
|
}}
|
|
836
1106
|
|
|
837
1107
|
/* ---- Card Actions ---- */
|
|
838
|
-
.card-actions {{ display: flex; gap:
|
|
1108
|
+
.card-actions {{ display: flex; gap: 10px; align-items: center; justify-content: flex-start; flex-wrap: wrap; }}
|
|
839
1109
|
.btn-read {{
|
|
840
|
-
font-size:
|
|
841
|
-
padding:
|
|
842
|
-
transition: all
|
|
843
|
-
background: linear-gradient(135deg,
|
|
844
|
-
box-shadow: 0
|
|
1110
|
+
font-size: 16px; color: #fff; text-decoration: none;
|
|
1111
|
+
padding: 8px 20px; border: none; border-radius: 8px;
|
|
1112
|
+
transition: all var(--transition); font-weight: 600;
|
|
1113
|
+
background: linear-gradient(135deg, var(--accent), #a07828);
|
|
1114
|
+
box-shadow: 0 2px 8px #8b691420;
|
|
845
1115
|
}}
|
|
846
|
-
.btn-read:hover {{ background: linear-gradient(135deg, #6b4c1e,
|
|
1116
|
+
.btn-read:hover {{ background: linear-gradient(135deg, #6b4c1e, var(--accent)); box-shadow: 0 4px 14px #8b691430; color: #fff; transform: translateY(-1px); }}
|
|
1117
|
+
.btn-read .arrow {{ display: inline-block; transition: transform .2s; }}
|
|
1118
|
+
.btn-read:hover .arrow {{ transform: translateX(3px); }}
|
|
847
1119
|
.btn-toggle {{
|
|
848
|
-
font-size:
|
|
849
|
-
padding:
|
|
850
|
-
transition: all
|
|
1120
|
+
font-size: 16px; color: var(--text-muted); background: var(--bg-card); border: 1px solid var(--border);
|
|
1121
|
+
padding: 8px 20px; border-radius: 8px; cursor: pointer;
|
|
1122
|
+
transition: all var(--transition); font-weight: 600; white-space: nowrap;
|
|
851
1123
|
}}
|
|
852
|
-
.btn-toggle:hover {{ color: #
|
|
853
|
-
.card.read .btn-toggle {{ color: #6b8f5e; border-color: #
|
|
854
|
-
.card.read .toggle
|
|
1124
|
+
.btn-toggle:hover {{ color: #5c4a32; border-color: #c8b898; background: var(--bg-soft); transform: translateY(-1px); }}
|
|
1125
|
+
.card.read .btn-toggle {{ color: #6b8f5e; border-color: #6b8f5e40; }}
|
|
1126
|
+
.card.read .btn-toggle:hover {{ background: #6b8f5e08; border-color: #6b8f5e60; }}
|
|
1127
|
+
.card.read .toggle-label::before {{ content: '↩ 取消已读'; }}
|
|
855
1128
|
.toggle-label::before {{ content: '✓ 标记已读'; }}
|
|
856
1129
|
|
|
857
1130
|
/* ---- Favorite ---- */
|
|
858
1131
|
.btn-fav {{
|
|
859
|
-
font-size:
|
|
860
|
-
padding:
|
|
861
|
-
transition: all
|
|
1132
|
+
font-size: 16px; background: var(--bg-card); border: 1px solid var(--border);
|
|
1133
|
+
padding: 8px 20px; border-radius: 8px; cursor: pointer;
|
|
1134
|
+
transition: all var(--transition); color: var(--text-muted); font-weight: 600; white-space: nowrap;
|
|
862
1135
|
}}
|
|
863
|
-
.btn-fav:hover {{ color:
|
|
864
|
-
.btn-fav.faved {{ color:
|
|
1136
|
+
.btn-fav:hover {{ color: var(--accent-light); border-color: #c8963e60; background: var(--accent-glow); transform: translateY(-1px); }}
|
|
1137
|
+
.btn-fav.faved {{ color: var(--accent-light); border-color: #c8963e50; background: #c8963e10; }}
|
|
1138
|
+
.fav-label::before {{ content: '☆ 收藏'; }}
|
|
1139
|
+
.btn-fav.faved .fav-label::before {{ content: '★ 已收藏'; }}
|
|
865
1140
|
.fav-empty {{
|
|
866
|
-
text-align: center; padding:
|
|
867
|
-
font-size:
|
|
1141
|
+
text-align: center; padding: 64px 20px; color: var(--text-faint);
|
|
1142
|
+
font-size: 18px; line-height: 2.2;
|
|
1143
|
+
background: var(--bg-soft); border-radius: var(--radius); border: 1px dashed #d4c5a0;
|
|
868
1144
|
}}
|
|
869
1145
|
|
|
870
|
-
/* ----
|
|
871
|
-
.
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1146
|
+
/* ---- Briefing ---- */
|
|
1147
|
+
.briefing {{
|
|
1148
|
+
background: linear-gradient(135deg, var(--bg-soft), var(--bg-card));
|
|
1149
|
+
border: 1px solid #d4c5a0; border-radius: var(--radius);
|
|
1150
|
+
padding: 20px 24px; margin-bottom: 24px;
|
|
1151
|
+
border-left: 4px solid var(--accent-light);
|
|
1152
|
+
box-shadow: var(--shadow-sm);
|
|
1153
|
+
}}
|
|
1154
|
+
.briefing-title {{
|
|
1155
|
+
font-size: 18px; font-weight: 700; color: var(--accent); margin-bottom: 8px;
|
|
1156
|
+
display: flex; align-items: center; gap: 6px;
|
|
1157
|
+
}}
|
|
1158
|
+
.briefing-text {{
|
|
1159
|
+
font-size: 18px; color: #5c4a32; line-height: 1.8;
|
|
876
1160
|
}}
|
|
877
|
-
.search-box:focus {{ border-color: #c8963e; box-shadow: 0 0 0 3px #c8963e15; }}
|
|
878
|
-
.search-box::placeholder {{ color: #b8a080; }}
|
|
879
1161
|
|
|
880
1162
|
/* ---- Footer ---- */
|
|
881
1163
|
.footer {{
|
|
882
|
-
text-align: center; padding:
|
|
883
|
-
font-size:
|
|
1164
|
+
text-align: center; padding: 36px 20px 8px; color: var(--text-faint);
|
|
1165
|
+
font-size: 16px; margin-top: 32px;
|
|
884
1166
|
}}
|
|
885
1167
|
|
|
1168
|
+
/* ---- Scrollbar ---- */
|
|
1169
|
+
::-webkit-scrollbar {{ width: 8px; }}
|
|
1170
|
+
::-webkit-scrollbar-track {{ background: var(--bg); }}
|
|
1171
|
+
::-webkit-scrollbar-thumb {{ background: #d4c5a0; border-radius: 4px; }}
|
|
1172
|
+
::-webkit-scrollbar-thumb:hover {{ background: var(--accent-light); }}
|
|
1173
|
+
|
|
886
1174
|
/* ---- Responsive ---- */
|
|
887
|
-
@media (max-width:
|
|
888
|
-
.container {{ padding: 14px; }}
|
|
889
|
-
.header {{ padding: 28px 14px
|
|
890
|
-
.header h1 {{ font-size:
|
|
891
|
-
.card {{ padding: 14px 16px; }}
|
|
1175
|
+
@media (max-width: 768px) {{
|
|
1176
|
+
.container {{ padding: 16px 14px 32px; }}
|
|
1177
|
+
.header {{ padding: 28px 14px 24px; margin-bottom: 16px; }}
|
|
1178
|
+
.header h1 {{ font-size: 26px; }}
|
|
1179
|
+
.card {{ padding: 14px 16px; border-radius: var(--radius-sm); }}
|
|
1180
|
+
.card-title {{ font-size: 19px; }}
|
|
1181
|
+
.tabs {{ min-width: 100%; }}
|
|
1182
|
+
.stats-bar {{ width: 100%; justify-content: center; }}
|
|
1183
|
+
.card-actions {{ flex-direction: column; align-items: stretch; gap: 8px; }}
|
|
1184
|
+
.btn-read, .btn-toggle, .btn-fav {{ text-align: center; }}
|
|
1185
|
+
}}
|
|
1186
|
+
@media (max-width: 480px) {{
|
|
892
1187
|
.tabs {{ gap: 2px; padding: 4px; }}
|
|
893
|
-
.tab-btn {{ padding: 7px
|
|
894
|
-
.
|
|
1188
|
+
.tab-btn {{ padding: 7px 10px; font-size: 17px; }}
|
|
1189
|
+
.briefing {{ padding: 14px 16px; }}
|
|
895
1190
|
}}
|
|
896
1191
|
</style>
|
|
897
1192
|
</head>
|
|
@@ -899,40 +1194,42 @@ body {{
|
|
|
899
1194
|
<div class="container">
|
|
900
1195
|
|
|
901
1196
|
<div class="header">
|
|
902
|
-
<
|
|
1197
|
+
<button id="theme-toggle" title="切换暗色" style="position:absolute;top:0;right:0;background:var(--bg-card);border:1px solid var(--border);border-radius:8px;padding:6px 10px;cursor:pointer;font-size: 20px;box-shadow:var(--shadow-sm);transition:all var(--transition);z-index:5;">🌙</button>
|
|
1198
|
+
<h1>公众号日报 — {date_str}</h1>
|
|
903
1199
|
<div class="meta">
|
|
904
|
-
<span>共 <strong>{total}</strong> 篇文章 {', '.join(f'{TOPIC_LABELS[t][2]} {len(topics[t])}' for t in TOPIC_ORDER if t in topics)}</span>
|
|
1200
|
+
<span>共 <strong>{total}</strong> 篇文章 · {', '.join(f'{TOPIC_LABELS[t][2]} {len(topics[t])}' for t in TOPIC_ORDER if t in topics)}</span>
|
|
905
1201
|
{action_link}
|
|
906
|
-
<span style="font-size:12px;color:#b8a080;display:flex;align-items:center;gap:6px">
|
|
907
|
-
<button class="btn-clear" id="btn-mode-md" onclick="setViewMode('md')" style="font-size:11px;padding:3px 10px">📝</button>
|
|
908
|
-
<button class="btn-clear" id="btn-mode-html" onclick="setViewMode('html')" style="font-size:11px;padding:3px 10px">🎨</button>
|
|
909
|
-
<button class="btn-clear" onclick="exportFav()" id="btn-export-fav" style="font-size:11px;padding:3px 10px">📥</button>
|
|
910
|
-
</span>
|
|
911
1202
|
</div>
|
|
912
1203
|
</div>
|
|
913
1204
|
|
|
914
|
-
|
|
915
|
-
<span class="stats-group">
|
|
916
|
-
<span>📊 <strong id="unread-count">-</strong> 篇未读 / {total} 篇</span>
|
|
917
|
-
<button class="btn-clear" id="btn-filter-unread" onclick="toggleUnreadFilter()" style="font-weight:600">👁 仅看未读</button>
|
|
918
|
-
</span>
|
|
919
|
-
<span class="stats-group">
|
|
920
|
-
<button class="btn-clear" onclick="markAllRead()">✅ 全部标为已读</button>
|
|
921
|
-
<button class="btn-clear" onclick="clearAll()">🔄 重置</button>
|
|
922
|
-
</span>
|
|
923
|
-
<span class="stats-group">
|
|
924
|
-
<span id="sync-indicator" style="display:none;font-size:12px;color:#6b8f5e;font-weight:600">🟢 已同步</span>
|
|
925
|
-
</span>
|
|
1205
|
+
{briefing_html}
|
|
926
1206
|
|
|
927
1207
|
<input type="text" class="search-box" placeholder="🔍 搜索文章标题、来源、标签..." oninput="doSearch(this.value)">
|
|
928
1208
|
|
|
929
|
-
<
|
|
1209
|
+
<div class="toolbar">
|
|
1210
|
+
<div class="stats-bar">
|
|
1211
|
+
<span class="stats-group">
|
|
1212
|
+
<span>📊 <strong id="unread-count">-</strong>/{total} 未读</span>
|
|
1213
|
+
</span>
|
|
1214
|
+
<span class="stats-divider"></span>
|
|
1215
|
+
<span class="stats-group">
|
|
1216
|
+
<button class="btn-clear" id="btn-filter-unread" onclick="toggleUnreadFilter()">👁 仅未读</button>
|
|
1217
|
+
<button class="btn-clear" onclick="markAllRead()">✅ 全部已读</button>
|
|
1218
|
+
<button class="btn-clear" onclick="clearAll()">🔄 重置</button>
|
|
1219
|
+
</span>
|
|
1220
|
+
<span class="stats-group" style="margin-left:auto">
|
|
1221
|
+
<button class="btn-clear" onclick="exportFav()" id="btn-export-fav" title="导出收藏">⭐ 收藏</button>
|
|
1222
|
+
<span id="sync-indicator" style="display:none;font-size: 16px;color:#6b8f5e;font-weight:600">🟢 已同步</span>
|
|
1223
|
+
</span>
|
|
1224
|
+
</div>
|
|
1225
|
+
<nav class="tabs">
|
|
930
1226
|
{''.join(nav_items)}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
</nav>
|
|
1227
|
+
<button class="tab-btn" data-topic="收藏"
|
|
1228
|
+
style="--accent: #fbbf24" onclick="switchTab('收藏')">
|
|
1229
|
+
⭐ 收藏 <span class="count" id="fav-count">0</span>
|
|
1230
|
+
</button>
|
|
1231
|
+
</nav>
|
|
1232
|
+
</div>
|
|
936
1233
|
|
|
937
1234
|
<section class="topic-section" id="section-收藏" data-topic="收藏">
|
|
938
1235
|
<h2 class="topic-heading" style="--accent: #fbbf24">
|
|
@@ -1023,7 +1320,7 @@ function switchTab(topic) {{
|
|
|
1023
1320
|
const btn = document.querySelector(`.tab-btn[data-topic="${{topic}}"]`);
|
|
1024
1321
|
if (btn) btn.classList.add('active');
|
|
1025
1322
|
sessionStorage.setItem('weflow_tab_{date_str}', topic);
|
|
1026
|
-
|
|
1323
|
+
try {{ history.replaceState(null, '', '#' + topic); }} catch(e) {{}}
|
|
1027
1324
|
}}
|
|
1028
1325
|
function doSearch(query) {{
|
|
1029
1326
|
const q = query.toLowerCase().trim();
|
|
@@ -1103,11 +1400,9 @@ function applyFavState() {{
|
|
|
1103
1400
|
const id = card.getAttribute('data-id');
|
|
1104
1401
|
if (favs.includes(id)) {{
|
|
1105
1402
|
btn.classList.add('faved');
|
|
1106
|
-
btn.innerHTML = '★';
|
|
1107
1403
|
card.classList.add('faved');
|
|
1108
1404
|
}} else {{
|
|
1109
1405
|
btn.classList.remove('faved');
|
|
1110
|
-
btn.innerHTML = '☆';
|
|
1111
1406
|
card.classList.remove('faved');
|
|
1112
1407
|
}}
|
|
1113
1408
|
}});
|
|
@@ -1135,14 +1430,6 @@ function renderFavSection() {{
|
|
|
1135
1430
|
container.innerHTML = html || '<div class="fav-empty">⭐ 暂无收藏文章</div>';
|
|
1136
1431
|
}}
|
|
1137
1432
|
|
|
1138
|
-
const VIEW_MODE_KEY = 'weflow_vmode_{date_str}';
|
|
1139
|
-
function getViewMode() {{
|
|
1140
|
-
return localStorage.getItem(VIEW_MODE_KEY) || 'html';
|
|
1141
|
-
}}
|
|
1142
|
-
function setViewMode(mode) {{
|
|
1143
|
-
localStorage.setItem(VIEW_MODE_KEY, mode);
|
|
1144
|
-
applyViewMode();
|
|
1145
|
-
}}
|
|
1146
1433
|
const UNREAD_KEY = 'weflow_unread_{date_str}';
|
|
1147
1434
|
let unreadFilterOn = sessionStorage.getItem(UNREAD_KEY) === '1';
|
|
1148
1435
|
function toggleUnreadFilter() {{
|
|
@@ -1152,10 +1439,10 @@ function toggleUnreadFilter() {{
|
|
|
1152
1439
|
if (btn) {{
|
|
1153
1440
|
if (unreadFilterOn) {{
|
|
1154
1441
|
btn.classList.add('active-mode');
|
|
1155
|
-
btn.textContent = '👁
|
|
1442
|
+
btn.textContent = '👁 仅未读 ✓';
|
|
1156
1443
|
}} else {{
|
|
1157
1444
|
btn.classList.remove('active-mode');
|
|
1158
|
-
btn.textContent = '👁
|
|
1445
|
+
btn.textContent = '👁 仅未读';
|
|
1159
1446
|
}}
|
|
1160
1447
|
}}
|
|
1161
1448
|
applyUnreadFilter();
|
|
@@ -1168,29 +1455,13 @@ function applyUnreadFilter() {{
|
|
|
1168
1455
|
}}
|
|
1169
1456
|
|
|
1170
1457
|
function applyViewMode() {{
|
|
1171
|
-
|
|
1172
|
-
const btnMd = document.getElementById('btn-mode-md');
|
|
1173
|
-
const btnHtml = document.getElementById('btn-mode-html');
|
|
1174
|
-
if (btnMd && btnHtml) {{
|
|
1175
|
-
btnMd.classList.toggle('active-mode', mode === 'md');
|
|
1176
|
-
btnHtml.classList.toggle('active-mode', mode === 'html');
|
|
1177
|
-
}}
|
|
1178
|
-
// Update all article title links and read buttons
|
|
1458
|
+
// 默认 HTML 渲染模式,所有链接指向 article.html
|
|
1179
1459
|
document.querySelectorAll('.card-title a, .btn-read').forEach(a => {{
|
|
1180
1460
|
const card = a.closest('.card');
|
|
1181
1461
|
if (!card) return;
|
|
1182
1462
|
const id = card.getAttribute('data-id');
|
|
1183
1463
|
if (!id) return;
|
|
1184
|
-
|
|
1185
|
-
const newHref = 'article.html?file=' + encodeURIComponent(id) + '&date={date_str}';
|
|
1186
|
-
if (a.getAttribute('data-md-href') === null) {{
|
|
1187
|
-
a.setAttribute('data-md-href', a.getAttribute('href') || '');
|
|
1188
|
-
}}
|
|
1189
|
-
a.setAttribute('href', newHref);
|
|
1190
|
-
}} else {{
|
|
1191
|
-
const mdHref = a.getAttribute('data-md-href');
|
|
1192
|
-
if (mdHref) a.setAttribute('href', mdHref);
|
|
1193
|
-
}}
|
|
1464
|
+
a.setAttribute('href', 'article.html?file=' + encodeURIComponent(id) + '&date={date_str}');
|
|
1194
1465
|
}});
|
|
1195
1466
|
}}
|
|
1196
1467
|
|
|
@@ -1254,7 +1525,7 @@ function exportFav() {{
|
|
|
1254
1525
|
for (const [k,v] of Object.entries(serverState)) {{
|
|
1255
1526
|
if (!local[k]) local[k] = true; // 只补充,不删除
|
|
1256
1527
|
}}
|
|
1257
|
-
//
|
|
1528
|
+
// 本地已读也同步到服务端
|
|
1258
1529
|
for (const [k,v] of Object.entries(local)) {{
|
|
1259
1530
|
if (!(k in serverState)) {{
|
|
1260
1531
|
fetch('/api/read/toggle', {{method:'POST',headers:{{'Content-Type':'application/json'}},body:JSON.stringify({{id:k}})}}).catch(()=>{{}});
|
|
@@ -1272,7 +1543,48 @@ function exportFav() {{
|
|
|
1272
1543
|
const savedTab = sessionStorage.getItem('weflow_tab_{date_str}');
|
|
1273
1544
|
const hash = window.location.hash.slice(1);
|
|
1274
1545
|
switchTab(hash || savedTab || 'AI');
|
|
1546
|
+
initDarkMode(); initKeyboard();
|
|
1275
1547
|
}})();
|
|
1548
|
+
|
|
1549
|
+
// ==== Dark Mode ====
|
|
1550
|
+
function initDarkMode() {{
|
|
1551
|
+
const toggle = document.getElementById('theme-toggle');
|
|
1552
|
+
if (!toggle) return;
|
|
1553
|
+
toggle.addEventListener('click', () => {{
|
|
1554
|
+
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
1555
|
+
document.documentElement.setAttribute('data-theme', isDark ? '' : 'dark');
|
|
1556
|
+
toggle.textContent = isDark ? '\U0001f319' : '\u2600\ufe0f';
|
|
1557
|
+
localStorage.setItem('theme', isDark ? 'light' : 'dark');
|
|
1558
|
+
}});
|
|
1559
|
+
const saved = localStorage.getItem('theme');
|
|
1560
|
+
if (saved === 'dark') {{
|
|
1561
|
+
document.documentElement.setAttribute('data-theme', 'dark');
|
|
1562
|
+
toggle.textContent = '\u2600\ufe0f';
|
|
1563
|
+
}}
|
|
1564
|
+
}}
|
|
1565
|
+
|
|
1566
|
+
// ==== Keyboard Shortcuts ====
|
|
1567
|
+
function initKeyboard() {{
|
|
1568
|
+
const cards = document.querySelectorAll('.card');
|
|
1569
|
+
let focused = -1;
|
|
1570
|
+
document.addEventListener('keydown', (e) => {{
|
|
1571
|
+
if (e.target.closest('input, textarea, select, [contenteditable]')) return;
|
|
1572
|
+
const cds = document.querySelectorAll('.card');
|
|
1573
|
+
if (e.key === 'j' || e.key === 'J') {{ focused = Math.min(focused + 1, cds.length - 1); focusCard(cds); }}
|
|
1574
|
+
else if (e.key === 'k' || e.key === 'K') {{ focused = Math.max(focused - 1, 0); focusCard(cds); }}
|
|
1575
|
+
else if (e.key === 'Enter' && focused >= 0) {{ cds[focused]?.querySelector('a[href]')?.click(); }}
|
|
1576
|
+
else if (e.key === 'Escape') {{ focused = -1; focusCard(cds); document.getElementById('search-input')?.focus(); }}
|
|
1577
|
+
else if (e.key === 'f' || e.key === 'F') {{ const id = cds[focused]?.getAttribute('data-id'); if (id) toggleFav(id); }}
|
|
1578
|
+
}});
|
|
1579
|
+
function focusCard(cds) {{
|
|
1580
|
+
cds.forEach((c, i) => {{
|
|
1581
|
+
c.style.outline = i === focused ? '2px solid var(--accent)' : '';
|
|
1582
|
+
c.style.transform = i === focused ? 'scale(1.01)' : '';
|
|
1583
|
+
c.style.boxShadow = i === focused ? '0 4px 20px var(--accent-glow)' : '';
|
|
1584
|
+
if (i === focused) c.scrollIntoView({{block:'center',behavior:'smooth'}});
|
|
1585
|
+
}});
|
|
1586
|
+
}}
|
|
1587
|
+
}}
|
|
1276
1588
|
</script>
|
|
1277
1589
|
</body>
|
|
1278
1590
|
</html>'''
|
|
@@ -1339,15 +1651,21 @@ def main():
|
|
|
1339
1651
|
with open(out_path, 'w', encoding='utf-8') as f:
|
|
1340
1652
|
f.write(html)
|
|
1341
1653
|
|
|
1342
|
-
#
|
|
1654
|
+
# 生成行动建议 HTML
|
|
1655
|
+
action_md_path = os.path.join(date_dir, '行动建议.md')
|
|
1656
|
+
action_html_path = os.path.join(date_dir, '行动建议.html')
|
|
1657
|
+
if os.path.exists(action_md_path):
|
|
1658
|
+
generate_action_html(date_str, action_md_path, action_html_path)
|
|
1659
|
+
print(f'✓ 行动建议 HTML 生成完成: {action_html_path}')
|
|
1660
|
+
|
|
1661
|
+
# 生成 article.html — 从永久模板复制
|
|
1343
1662
|
article_html_path = os.path.join(date_dir, 'article.html')
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
generate_article_viewer(article_html_path)
|
|
1663
|
+
template = os.path.join(SOURCE_ROOT, '.template', 'article.html')
|
|
1664
|
+
if os.path.exists(template):
|
|
1665
|
+
shutil.copy2(template, article_html_path)
|
|
1666
|
+
else:
|
|
1667
|
+
generate_article_viewer(article_html_path)
|
|
1668
|
+
_inline_marked_js(article_html_path)
|
|
1351
1669
|
|
|
1352
1670
|
# 注入本地图片映射,优先用本地图片避免代理延迟
|
|
1353
1671
|
image_map_path = os.path.join(date_dir, '.image_map.json')
|
|
@@ -1357,7 +1675,9 @@ def main():
|
|
|
1357
1675
|
if img_map and os.path.exists(article_html_path):
|
|
1358
1676
|
with open(article_html_path, 'r', encoding='utf-8') as f:
|
|
1359
1677
|
article_html = f.read()
|
|
1360
|
-
|
|
1678
|
+
# Escape < in JSON to prevent XSS via </script> injection
|
|
1679
|
+
img_map_json = json.dumps(img_map, ensure_ascii=False).replace('<', '\\u003c')
|
|
1680
|
+
img_map_js = '<script>window._IMG_MAP=' + img_map_json + ';</script>'
|
|
1361
1681
|
# 注入到 </head> 之前
|
|
1362
1682
|
article_html = article_html.replace('</head>', img_map_js + '\n</head>', 1)
|
|
1363
1683
|
with open(article_html_path, 'w', encoding='utf-8') as f:
|