md2wx-cli 0.2.0__tar.gz → 0.2.1__tar.gz

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.
Files changed (33) hide show
  1. md2wx_cli-0.2.1/PKG-INFO +171 -0
  2. md2wx_cli-0.2.1/README.md +156 -0
  3. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/pyproject.toml +1 -1
  4. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/__init__.py +1 -1
  5. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/converter.py +36 -3
  6. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/styler.py +19 -1
  7. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/themes/bauhaus.yaml +208 -207
  8. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/themes/bold-green.yaml +199 -198
  9. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/themes/bold-navy.yaml +198 -197
  10. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/themes/default.yaml +1 -0
  11. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/themes/github-tech.yaml +2 -0
  12. md2wx_cli-0.2.1/src/md2wx_cli.egg-info/PKG-INFO +171 -0
  13. md2wx_cli-0.2.0/PKG-INFO +0 -184
  14. md2wx_cli-0.2.0/README.md +0 -169
  15. md2wx_cli-0.2.0/src/md2wx_cli.egg-info/PKG-INFO +0 -184
  16. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/LICENSE +0 -0
  17. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/setup.cfg +0 -0
  18. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/cli.py +0 -0
  19. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/clipboard.py +0 -0
  20. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/config.py +0 -0
  21. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/containers.py +0 -0
  22. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/fetcher.py +0 -0
  23. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/gallery.py +0 -0
  24. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/publisher.py +0 -0
  25. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/theme.py +0 -0
  26. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/theme_extractor.py +0 -0
  27. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/validator.py +0 -0
  28. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx/wechat_api.py +0 -0
  29. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx_cli.egg-info/SOURCES.txt +0 -0
  30. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx_cli.egg-info/dependency_links.txt +0 -0
  31. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx_cli.egg-info/entry_points.txt +0 -0
  32. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx_cli.egg-info/requires.txt +0 -0
  33. {md2wx_cli-0.2.0 → md2wx_cli-0.2.1}/src/md2wx_cli.egg-info/top_level.txt +0 -0
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.4
2
+ Name: md2wx-cli
3
+ Version: 0.2.1
4
+ Summary: 轻量级 Markdown 转微信公众号 HTML CLI 工具(支持剪贴板直达、主题提取、WeChatSync 联动)
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: markdown>=3.10
10
+ Requires-Dist: beautifulsoup4>=4.14
11
+ Requires-Dist: cssutils>=2.11
12
+ Requires-Dist: pyyaml>=6.0
13
+ Requires-Dist: Pygments>=2.17
14
+ Dynamic: license-file
15
+
16
+ # md2wx-cli
17
+
18
+ [![PyPI](https://img.shields.io/pypi/v/md2wx-cli)](https://pypi.org/project/md2wx-cli/)
19
+ [![Python](https://img.shields.io/pypi/pyversions/md2wx-cli)](https://pypi.org/project/md2wx-cli/)
20
+ [![License](https://img.shields.io/pypi/l/md2wx-cli)](LICENSE)
21
+
22
+ > 轻量、确定性、高保真的 Markdown 转微信公众号排版 CLI。
23
+
24
+ 从写作到发布全链路命令行闭环:内联 CSS 排版、5 套精选主题、多主题画廊对比、推文抓取逆向、主题智能提取、官方 API 直传草稿箱、排版合规体检。不依赖任何网页编辑器,改一个错别字只需重跑一条命令。
25
+
26
+ ## 安装
27
+
28
+ 要求 Python ≥ 3.11。
29
+
30
+ ```bash
31
+ pip install md2wx-cli
32
+ ```
33
+
34
+ 或免安装直接运行:
35
+
36
+ ```bash
37
+ uvx md2wx-cli article.md -c
38
+ ```
39
+
40
+ > 命令入口名为 `md2wx`。PyPI 包名 `md2wx-cli` 与 import 名 `md2wx` 分离(`md2wx` 在 PyPI 上已被其他项目占用)。
41
+
42
+ ## 快速开始
43
+
44
+ ```bash
45
+ # 转换并写入系统剪贴板,公众号编辑器 Ctrl+V 直接粘贴
46
+ md2wx article.md -c
47
+
48
+ # 输出 HTML 文件
49
+ md2wx article.md -o output.html
50
+
51
+ # 切换主题
52
+ md2wx article.md -t github-tech -c
53
+
54
+ # 生成完整 <html> 独立页面,双击即可预览
55
+ md2wx article.md -o preview.html --full-page
56
+
57
+ # 支持标准输入管道
58
+ cat article.md | md2wx -c
59
+ ```
60
+
61
+ 转换器处理的不只是 Markdown 渲染:中英文之间自动加空格、代码块语法高亮(Pygments)、外链自动转为文末脚注(微信正文不允许外链可点击)、全内联 CSS 与暗黑模式适配——这些全是微信编辑器的兼容性硬需求。
62
+
63
+ ## 主题系统
64
+
65
+ 内置 5 套主题,`-t` 一键切换:
66
+
67
+ | 主题 | 风格 | 适合内容 |
68
+ | :--- | :--- | :--- |
69
+ | `default` | 现代极简杂志编辑风 | 通用,默认选项 |
70
+ | `github-tech` | GitHub 开发者文档风(OneDark 代码高亮) | 教程、源码解析 |
71
+ | `bauhaus` | 包豪斯几何设计风 | 设计类、观点文 |
72
+ | `bold-green` | 清新高对比科技绿 | 环保、健康主题 |
73
+ | `bold-navy` | 深邃藏青商务编辑风 | 深度长文、行业分析 |
74
+
75
+ 不确定选哪套?生成全主题交互画廊,浏览器里秒级切换对比,选中即一键复制:
76
+
77
+ ```bash
78
+ md2wx gallery article.md -o gallery.html
79
+ ```
80
+
81
+ 主题是 YAML 文件(字体栈、配色、标题层级、代码块样式全部可调),也支持从外部逆向提取:
82
+
83
+ ```bash
84
+ # 从一篇排版不错的微信推文学一套主题
85
+ md2wx learn-theme --url "https://mp.weixin.qq.com/s/xxxxxx" -n brand-editorial
86
+
87
+ # 或从本地设计稿目录提取
88
+ md2wx learn-theme --dir ./design_folder -n brand-editorial
89
+
90
+ # 直接套用
91
+ md2wx article.md -t brand-editorial -c
92
+ ```
93
+
94
+ ## 发布到公众号草稿箱
95
+
96
+ 前置条件:公众号 AppID / AppSecret(微信公众平台「设置与开发 → 基本配置」获取),且将本机出口 IP 加入后台 **IP 白名单**(否则报 `errcode=40164`)。
97
+
98
+ 首次配置凭据,永久保存至 `~/.md2wx/config.yaml`:
99
+
100
+ ```bash
101
+ md2wx config set wechat.appid "wx1234567890abcdef"
102
+ md2wx config set wechat.secret "你的AppSecret"
103
+ ```
104
+
105
+ 一键直传(自动上传本地配图到微信永久图床并替换链接、上传封面、创建草稿):
106
+
107
+ ```bash
108
+ md2wx article.md -p --title "文章标题" --author "作者" --cover images/cover.png
109
+ ```
110
+
111
+ 命令返回 `media_id`,登录 mp.weixin.qq.com 在草稿箱中确认后手动群发。**直传只写草稿箱,不会自动群发。**
112
+
113
+ 凭据读取优先级:命令行参数(`--appid` / `--secret`)> 环境变量 > `~/.md2wx/config.yaml`,方便在 CI 中注入。
114
+
115
+ > 注意:`--cover` 路径相对于输入 Markdown 文件所在目录解析,建议传绝对路径或相对 md 文件的路径。
116
+
117
+ ## 发布前体检
118
+
119
+ ```bash
120
+ md2wx check article.md
121
+ ```
122
+
123
+ 内置 16+ 条微信平台硬规则检测:失效的本地图片引用、未转脚注的裸外链、编辑器不支持的非法 CSS(`position: fixed`、`display: grid`、`animation` 等)、代码块格式保护。体检通过再直传,基本不会翻车。
124
+
125
+ ## 抓取推文逆向
126
+
127
+ ```bash
128
+ md2wx fetch "https://mp.weixin.qq.com/s/xxxxxx" -o draft.md
129
+ ```
130
+
131
+ 配图自动下载至 `draft-assets/`,居中高亮、白底卡片、实心横幅被识别还原为对应的容器语法。学习他人排版或做二次加工,从这一步开始。
132
+
133
+ ## 容器排版语法
134
+
135
+ 标准 Markdown 之外,原生支持几种扩展容器(渲染为微信兼容的内联样式组件):
136
+
137
+ ```markdown
138
+ ::: banner 导语标题
139
+ 正文内容,支持 **加粗** 与 `代码`
140
+ :::
141
+
142
+ ::: card 白底描边卡片,适合承载补充说明或外链
143
+ https://github.com/example/repo
144
+ :::
145
+
146
+ > [!WARNING]
147
+ > GitHub 风格告示块同样支持,自动渲染为主题色提示卡。
148
+ ```
149
+
150
+ ## 配置文件
151
+
152
+ `~/.md2wx/config.yaml`:
153
+
154
+ ```yaml
155
+ wechat:
156
+ appid: "wx1234567890abcdef"
157
+ secret: "你的AppSecret"
158
+ author: "你的笔名"
159
+ ```
160
+
161
+ ## 开发
162
+
163
+ ```bash
164
+ git clone <repo-url> && cd md2wx-cli
165
+ pip install -e .
166
+ python -m pytest tests/
167
+ ```
168
+
169
+ ## License
170
+
171
+ MIT
@@ -0,0 +1,156 @@
1
+ # md2wx-cli
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/md2wx-cli)](https://pypi.org/project/md2wx-cli/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/md2wx-cli)](https://pypi.org/project/md2wx-cli/)
5
+ [![License](https://img.shields.io/pypi/l/md2wx-cli)](LICENSE)
6
+
7
+ > 轻量、确定性、高保真的 Markdown 转微信公众号排版 CLI。
8
+
9
+ 从写作到发布全链路命令行闭环:内联 CSS 排版、5 套精选主题、多主题画廊对比、推文抓取逆向、主题智能提取、官方 API 直传草稿箱、排版合规体检。不依赖任何网页编辑器,改一个错别字只需重跑一条命令。
10
+
11
+ ## 安装
12
+
13
+ 要求 Python ≥ 3.11。
14
+
15
+ ```bash
16
+ pip install md2wx-cli
17
+ ```
18
+
19
+ 或免安装直接运行:
20
+
21
+ ```bash
22
+ uvx md2wx-cli article.md -c
23
+ ```
24
+
25
+ > 命令入口名为 `md2wx`。PyPI 包名 `md2wx-cli` 与 import 名 `md2wx` 分离(`md2wx` 在 PyPI 上已被其他项目占用)。
26
+
27
+ ## 快速开始
28
+
29
+ ```bash
30
+ # 转换并写入系统剪贴板,公众号编辑器 Ctrl+V 直接粘贴
31
+ md2wx article.md -c
32
+
33
+ # 输出 HTML 文件
34
+ md2wx article.md -o output.html
35
+
36
+ # 切换主题
37
+ md2wx article.md -t github-tech -c
38
+
39
+ # 生成完整 <html> 独立页面,双击即可预览
40
+ md2wx article.md -o preview.html --full-page
41
+
42
+ # 支持标准输入管道
43
+ cat article.md | md2wx -c
44
+ ```
45
+
46
+ 转换器处理的不只是 Markdown 渲染:中英文之间自动加空格、代码块语法高亮(Pygments)、外链自动转为文末脚注(微信正文不允许外链可点击)、全内联 CSS 与暗黑模式适配——这些全是微信编辑器的兼容性硬需求。
47
+
48
+ ## 主题系统
49
+
50
+ 内置 5 套主题,`-t` 一键切换:
51
+
52
+ | 主题 | 风格 | 适合内容 |
53
+ | :--- | :--- | :--- |
54
+ | `default` | 现代极简杂志编辑风 | 通用,默认选项 |
55
+ | `github-tech` | GitHub 开发者文档风(OneDark 代码高亮) | 教程、源码解析 |
56
+ | `bauhaus` | 包豪斯几何设计风 | 设计类、观点文 |
57
+ | `bold-green` | 清新高对比科技绿 | 环保、健康主题 |
58
+ | `bold-navy` | 深邃藏青商务编辑风 | 深度长文、行业分析 |
59
+
60
+ 不确定选哪套?生成全主题交互画廊,浏览器里秒级切换对比,选中即一键复制:
61
+
62
+ ```bash
63
+ md2wx gallery article.md -o gallery.html
64
+ ```
65
+
66
+ 主题是 YAML 文件(字体栈、配色、标题层级、代码块样式全部可调),也支持从外部逆向提取:
67
+
68
+ ```bash
69
+ # 从一篇排版不错的微信推文学一套主题
70
+ md2wx learn-theme --url "https://mp.weixin.qq.com/s/xxxxxx" -n brand-editorial
71
+
72
+ # 或从本地设计稿目录提取
73
+ md2wx learn-theme --dir ./design_folder -n brand-editorial
74
+
75
+ # 直接套用
76
+ md2wx article.md -t brand-editorial -c
77
+ ```
78
+
79
+ ## 发布到公众号草稿箱
80
+
81
+ 前置条件:公众号 AppID / AppSecret(微信公众平台「设置与开发 → 基本配置」获取),且将本机出口 IP 加入后台 **IP 白名单**(否则报 `errcode=40164`)。
82
+
83
+ 首次配置凭据,永久保存至 `~/.md2wx/config.yaml`:
84
+
85
+ ```bash
86
+ md2wx config set wechat.appid "wx1234567890abcdef"
87
+ md2wx config set wechat.secret "你的AppSecret"
88
+ ```
89
+
90
+ 一键直传(自动上传本地配图到微信永久图床并替换链接、上传封面、创建草稿):
91
+
92
+ ```bash
93
+ md2wx article.md -p --title "文章标题" --author "作者" --cover images/cover.png
94
+ ```
95
+
96
+ 命令返回 `media_id`,登录 mp.weixin.qq.com 在草稿箱中确认后手动群发。**直传只写草稿箱,不会自动群发。**
97
+
98
+ 凭据读取优先级:命令行参数(`--appid` / `--secret`)> 环境变量 > `~/.md2wx/config.yaml`,方便在 CI 中注入。
99
+
100
+ > 注意:`--cover` 路径相对于输入 Markdown 文件所在目录解析,建议传绝对路径或相对 md 文件的路径。
101
+
102
+ ## 发布前体检
103
+
104
+ ```bash
105
+ md2wx check article.md
106
+ ```
107
+
108
+ 内置 16+ 条微信平台硬规则检测:失效的本地图片引用、未转脚注的裸外链、编辑器不支持的非法 CSS(`position: fixed`、`display: grid`、`animation` 等)、代码块格式保护。体检通过再直传,基本不会翻车。
109
+
110
+ ## 抓取推文逆向
111
+
112
+ ```bash
113
+ md2wx fetch "https://mp.weixin.qq.com/s/xxxxxx" -o draft.md
114
+ ```
115
+
116
+ 配图自动下载至 `draft-assets/`,居中高亮、白底卡片、实心横幅被识别还原为对应的容器语法。学习他人排版或做二次加工,从这一步开始。
117
+
118
+ ## 容器排版语法
119
+
120
+ 标准 Markdown 之外,原生支持几种扩展容器(渲染为微信兼容的内联样式组件):
121
+
122
+ ```markdown
123
+ ::: banner 导语标题
124
+ 正文内容,支持 **加粗** 与 `代码`
125
+ :::
126
+
127
+ ::: card 白底描边卡片,适合承载补充说明或外链
128
+ https://github.com/example/repo
129
+ :::
130
+
131
+ > [!WARNING]
132
+ > GitHub 风格告示块同样支持,自动渲染为主题色提示卡。
133
+ ```
134
+
135
+ ## 配置文件
136
+
137
+ `~/.md2wx/config.yaml`:
138
+
139
+ ```yaml
140
+ wechat:
141
+ appid: "wx1234567890abcdef"
142
+ secret: "你的AppSecret"
143
+ author: "你的笔名"
144
+ ```
145
+
146
+ ## 开发
147
+
148
+ ```bash
149
+ git clone <repo-url> && cd md2wx-cli
150
+ pip install -e .
151
+ python -m pytest tests/
152
+ ```
153
+
154
+ ## License
155
+
156
+ MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "md2wx-cli"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "轻量级 Markdown 转微信公众号 HTML CLI 工具(支持剪贴板直达、主题提取、WeChatSync 联动)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """md2wx — Markdown to WeChat HTML CLI tool."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.1"
@@ -36,6 +36,7 @@ class WeChatConverter:
36
36
  def __init__(self, theme: Optional[Theme] = None, theme_name: str = "default"):
37
37
  self._theme = theme if theme is not None else load_theme(theme_name)
38
38
  self._css_rules = get_inline_css_rules(self._theme)
39
+ self._pygments_style = self._resolve_pygments_style()
39
40
 
40
41
  def convert(self, markdown_text: str, base_dir: Optional[Path] = None) -> ConvertResult:
41
42
  title = self._extract_title(markdown_text)
@@ -48,8 +49,8 @@ class WeChatConverter:
48
49
  markdown_text = process_containers(markdown_text, accent)
49
50
  markdown_text = self._fix_cjk_spacing(markdown_text)
50
51
 
51
- # 2. 基础 Markdown → HTML 解析
52
- raw_html = self._markdown_to_html(markdown_text)
52
+ # 2. 基础 Markdown → HTML 解析(解包 codehilite 外层 div)
53
+ raw_html = self._unwrap_codehilite_div(self._markdown_to_html(markdown_text))
53
54
  soup = BeautifulSoup(raw_html, "html.parser")
54
55
 
55
56
  # 3. 本地图片路径转换与外框样式
@@ -132,9 +133,41 @@ class WeChatConverter:
132
133
  "markdown.extensions.sane_lists",
133
134
  "markdown.extensions.codehilite",
134
135
  ]
135
- configs = {"codehilite": {"linenums": False, "guess_lang": True, "noclasses": True}}
136
+ # 注意:extension_configs 的键必须与扩展注册路径完全一致,
137
+ # 使用短名 "codehilite" 在 Markdown 3.x 下会静默失效(noclasses/pygments_style 均不生效)
138
+ configs = {
139
+ "markdown.extensions.codehilite": {
140
+ "linenums": False,
141
+ "guess_lang": True,
142
+ "noclasses": True,
143
+ "pygments_style": self._pygments_style,
144
+ }
145
+ }
136
146
  return markdown.Markdown(extensions=exts, extension_configs=configs).convert(text)
137
147
 
148
+ def _resolve_pygments_style(self) -> str:
149
+ """解析代码高亮风格:主题显式声明优先,否则按 code_bg 亮度自动选择。"""
150
+ style = str(self._theme._raw_data.get("code_highlight_style", "")).strip()
151
+ if style:
152
+ return style
153
+ code_bg = str(self._theme.colors.get("code_bg", "#f8f8f8"))
154
+ m = re.search(r"^#?([0-9a-fA-F]{6})$", code_bg)
155
+ if m:
156
+ r, g, b = (int(m.group(1)[i : i + 2], 16) for i in (0, 2, 4))
157
+ # 相对亮度粗估:暗底用暗色系高亮风格,浅底用浅色系
158
+ if 0.299 * r + 0.587 * g + 0.114 * b < 128:
159
+ return "one-dark"
160
+ return "friendly"
161
+
162
+ def _unwrap_codehilite_div(self, raw_html: str) -> str:
163
+ """解包 codehilite/highlight 包装 div,避免其内联背景色在微信端形成色差外框。"""
164
+ return re.sub(
165
+ r'<div class="(?:codehilite|highlight)"[^>]*>\s*(<pre.*?</pre>)\s*</div>',
166
+ r"\1",
167
+ raw_html,
168
+ flags=re.DOTALL,
169
+ )
170
+
138
171
  def _process_images(self, soup: BeautifulSoup, base_dir: Optional[Path]) -> list[str]:
139
172
  images = []
140
173
  for img in soup.find_all("img"):
@@ -107,7 +107,15 @@ def apply_inline_styles(soup: BeautifulSoup, theme: Theme, css_rules: dict) -> N
107
107
  if extra:
108
108
  img["style"] = f"{est}; {'; '.join(extra)}" if est else "; ".join(extra)
109
109
 
110
- # 7. 代码块横向滚动保障
110
+ # 7. 代码块横向滚动保障;pre 显式声明文字色(内层 code 为 color: inherit,
111
+ # 若 pre 缺省 color 会继承正文灰,在深色代码底上完全不可读)
112
+ def _is_dark_bg(bg: str) -> bool:
113
+ m = re.search(r"([0-9a-fA-F]{6})", bg or "")
114
+ if not m:
115
+ return False
116
+ r, g, b = (int(m.group(1)[i : i + 2], 16) for i in (0, 2, 4))
117
+ return 0.299 * r + 0.587 * g + 0.114 * b < 128
118
+
111
119
  for pre in soup.find_all("pre"):
112
120
  for code in pre.find_all("code"):
113
121
  code["style"] = (
@@ -117,6 +125,16 @@ def apply_inline_styles(soup: BeautifulSoup, theme: Theme, css_rules: dict) -> N
117
125
  "color: inherit !important; padding: 0 !important; border: none !important; "
118
126
  "font-size: inherit; font-family: inherit; line-height: inherit"
119
127
  )
128
+ # 解析 pre 现有内联样式(注意 "background-color" 含 "color" 子串,须按键精确判断)
129
+ pre_items = [item.split(":", 1) for item in pre.get("style", "").split(";") if ":" in item]
130
+ sdict = {k.strip(): v.strip() for k, v in pre_items}
131
+ if "color" not in sdict:
132
+ bg = sdict.get("background") or sdict.get("background-color") or str(colors.get("code_bg", ""))
133
+ if _is_dark_bg(bg):
134
+ fallback = str(colors.get("code_color", "#e2e8f0"))
135
+ else:
136
+ fallback = str(colors.get("text", "#333333"))
137
+ pre["style"] = f"color: {fallback}; {pre.get('style', '')}" if pre.get("style") else f"color: {fallback}"
120
138
 
121
139
 
122
140
  def preserve_code_block_newlines(soup: BeautifulSoup) -> None: