665 lines
25 KiB
Python
665 lines
25 KiB
Python
"""
|
||
微信 Markdown 编辑器 GUI 模块
|
||
提供可视化编辑界面
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import tkinter as tk
|
||
from tkinter import ttk, messagebox, filedialog, scrolledtext, colorchooser
|
||
import tempfile
|
||
import webbrowser
|
||
import os
|
||
|
||
# 使用 tkinterweb 进行 HTML 预览(支持完整 HTML 渲染)
|
||
from tkinterweb import HtmlFrame
|
||
|
||
from markdown_editor import (
|
||
WechatMarkdownEditor, ThemeConfig, FontConfig,
|
||
CodeBlockConfig, EditorConfig
|
||
)
|
||
|
||
|
||
class MarkdownEditorWindow:
|
||
"""Markdown 编辑器窗口"""
|
||
|
||
def __init__(self, parent=None):
|
||
self.editor = WechatMarkdownEditor()
|
||
self.config = self.editor.get_config()
|
||
|
||
# 创建窗口
|
||
if parent:
|
||
self.window = tk.Toplevel(parent)
|
||
else:
|
||
self.window = tk.Tk()
|
||
|
||
self.window.title("微信 Markdown 编辑器")
|
||
self.window.geometry("1400x900")
|
||
self.window.minsize(1200, 700)
|
||
|
||
# 设置样式
|
||
self.style = ttk.Style()
|
||
self.style.configure("Title.TLabel", font=("Microsoft YaHei", 14, "bold"))
|
||
self.style.configure("Subtitle.TLabel", font=("Microsoft YaHei", 10))
|
||
|
||
self.create_widgets()
|
||
self.update_preview()
|
||
|
||
def create_widgets(self):
|
||
"""创建界面组件"""
|
||
# 主框架 - 使用 PanedWindow 实现可调整的分割
|
||
main_paned = ttk.PanedWindow(self.window, orient=tk.HORIZONTAL)
|
||
main_paned.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||
|
||
# === 左侧:编辑区 ===
|
||
left_frame = ttk.Frame(main_paned)
|
||
main_paned.add(left_frame, weight=1)
|
||
|
||
# 编辑区标题
|
||
ttk.Label(left_frame, text="Markdown 编辑区", style="Title.TLabel").pack(anchor=tk.W, pady=(0, 5))
|
||
|
||
# Markdown 编辑框
|
||
self.md_text = scrolledtext.ScrolledText(
|
||
left_frame,
|
||
wrap=tk.WORD,
|
||
font=("Consolas", 12),
|
||
undo=True,
|
||
maxundo=-1
|
||
)
|
||
self.md_text.pack(fill=tk.BOTH, expand=True)
|
||
self.md_text.bind("<KeyRelease>", lambda e: self.update_preview())
|
||
|
||
# 默认内容
|
||
default_content = """# 欢迎使用微信 Markdown 编辑器
|
||
|
||
这是一段示例文本,支持 **粗体**、*斜体*、`行内代码`。
|
||
|
||
## 代码示例
|
||
|
||
```python
|
||
def hello_world():
|
||
print("Hello, World!")
|
||
return True
|
||
```
|
||
|
||
## 列表
|
||
|
||
- 项目 1
|
||
- 项目 2
|
||
- 项目 3
|
||
|
||
## 引用
|
||
|
||
> 这是一段引用文本。
|
||
|
||
## 表格
|
||
|
||
| 列1 | 列2 | 列3 |
|
||
|-----|-----|-----|
|
||
| A | B | C |
|
||
| D | E | F |
|
||
|
||
---
|
||
|
||
在右侧设置面板中可以调整主题、字体、代码块样式等。
|
||
"""
|
||
self.md_text.insert("1.0", default_content)
|
||
|
||
# === 中间:预览区 ===
|
||
center_frame = ttk.Frame(main_paned)
|
||
main_paned.add(center_frame, weight=1)
|
||
|
||
# 预览区标题和工具栏
|
||
preview_header = ttk.Frame(center_frame)
|
||
preview_header.pack(fill=tk.X, pady=(0, 5))
|
||
|
||
ttk.Label(preview_header, text="实时预览", style="Title.TLabel").pack(side=tk.LEFT)
|
||
|
||
# 刷新按钮
|
||
ttk.Button(preview_header, text="🔄 刷新", command=self.update_preview).pack(side=tk.RIGHT, padx=5)
|
||
ttk.Button(preview_header, text="📋 复制 HTML", command=self.copy_html).pack(side=tk.RIGHT, padx=5)
|
||
ttk.Button(preview_header, text="💾 导出 HTML", command=self.export_html).pack(side=tk.RIGHT, padx=5)
|
||
|
||
# 预览区域
|
||
preview_notebook = ttk.Notebook(center_frame)
|
||
preview_notebook.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# HTML 渲染预览标签页
|
||
preview_frame = ttk.Frame(preview_notebook)
|
||
preview_notebook.add(preview_frame, text="效果预览")
|
||
|
||
# 使用 HtmlFrame 显示渲染后的 HTML
|
||
self.html_preview = HtmlFrame(preview_frame, messages_enabled=False)
|
||
self.html_preview.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# HTML 源码标签页
|
||
html_frame = ttk.Frame(preview_notebook)
|
||
preview_notebook.add(html_frame, text="HTML 源码")
|
||
|
||
self.html_text = scrolledtext.ScrolledText(
|
||
html_frame,
|
||
wrap=tk.NONE,
|
||
font=("Consolas", 10),
|
||
state=tk.DISABLED
|
||
)
|
||
self.html_text.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# 浏览器预览标签页
|
||
browser_frame = ttk.Frame(preview_notebook)
|
||
preview_notebook.add(browser_frame, text="浏览器预览")
|
||
|
||
ttk.Label(browser_frame, text="点击按钮在浏览器中预览效果:").pack(pady=20)
|
||
ttk.Button(browser_frame, text="🌐 在浏览器中打开", command=self.open_in_browser).pack(pady=10)
|
||
|
||
# === 右侧:设置面板 ===
|
||
right_frame = ttk.Frame(main_paned, width=350)
|
||
main_paned.add(right_frame, weight=0)
|
||
right_frame.pack_propagate(False)
|
||
|
||
# 设置面板标题
|
||
ttk.Label(right_frame, text="设置面板", style="Title.TLabel").pack(anchor=tk.W, pady=(0, 10))
|
||
|
||
# 创建设置区域的 Canvas 和 Scrollbar
|
||
settings_canvas = tk.Canvas(right_frame, highlightthickness=0)
|
||
scrollbar = ttk.Scrollbar(right_frame, orient=tk.VERTICAL, command=settings_canvas.yview)
|
||
settings_canvas.configure(yscrollcommand=scrollbar.set)
|
||
|
||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||
settings_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||
|
||
# 设置内容框架
|
||
self.settings_frame = ttk.Frame(settings_canvas)
|
||
settings_canvas.create_window((0, 0), window=self.settings_frame, anchor=tk.NW, width=330)
|
||
|
||
# 绑定滚动
|
||
self.settings_frame.bind("<Configure>", lambda e: settings_canvas.configure(scrollregion=settings_canvas.bbox("all")))
|
||
settings_canvas.bind("<Configure>", lambda e: settings_canvas.itemconfig(settings_canvas.find_all()[0], width=e.width-5))
|
||
|
||
# 创建设置区域
|
||
self.create_theme_settings()
|
||
self.create_font_settings()
|
||
self.create_code_block_settings()
|
||
self.create_custom_css_settings()
|
||
|
||
def create_theme_settings(self):
|
||
"""创建主题设置区域"""
|
||
theme_frame = ttk.LabelFrame(self.settings_frame, text="主题设置", padding=10)
|
||
theme_frame.pack(fill=tk.X, pady=(0, 10))
|
||
|
||
# 主题选择
|
||
ttk.Label(theme_frame, text="选择主题:").pack(anchor=tk.W)
|
||
|
||
self.theme_var = tk.StringVar(value=self.config.theme)
|
||
themes = self.editor.theme_manager.get_all_themes()
|
||
theme_names = [f"{t.id}:{t.name}" for t in themes]
|
||
|
||
self.theme_combo = ttk.Combobox(
|
||
theme_frame,
|
||
values=theme_names,
|
||
textvariable=self.theme_var,
|
||
state="readonly"
|
||
)
|
||
self.theme_combo.pack(fill=tk.X, pady=(5, 10))
|
||
self.theme_combo.bind("<<ComboboxSelected>>", self.on_theme_change)
|
||
|
||
# 主题色
|
||
color_frame = ttk.Frame(theme_frame)
|
||
color_frame.pack(fill=tk.X, pady=5)
|
||
|
||
ttk.Label(color_frame, text="主题色:").pack(side=tk.LEFT)
|
||
|
||
self.primary_color_var = tk.StringVar(value=self.editor.theme_manager.get_current_theme().primary_color)
|
||
self.color_btn = tk.Button(
|
||
color_frame,
|
||
text=" ",
|
||
bg=self.primary_color_var.get(),
|
||
width=3,
|
||
command=self.choose_primary_color
|
||
)
|
||
self.color_btn.pack(side=tk.LEFT, padx=5)
|
||
|
||
ttk.Entry(color_frame, textvariable=self.primary_color_var, width=10).pack(side=tk.LEFT)
|
||
ttk.Button(color_frame, text="应用", command=self.apply_primary_color).pack(side=tk.LEFT, padx=5)
|
||
|
||
def create_font_settings(self):
|
||
"""创建字体设置区域"""
|
||
font_frame = ttk.LabelFrame(self.settings_frame, text="字体设置", padding=10)
|
||
font_frame.pack(fill=tk.X, pady=(0, 10))
|
||
|
||
# 正文字体
|
||
ttk.Label(font_frame, text="正文字体:").pack(anchor=tk.W)
|
||
self.body_font_var = tk.StringVar(value=self.config.font.body_font)
|
||
ttk.Entry(font_frame, textvariable=self.body_font_var).pack(fill=tk.X, pady=(0, 5))
|
||
|
||
# 正文字号
|
||
size_frame = ttk.Frame(font_frame)
|
||
size_frame.pack(fill=tk.X, pady=5)
|
||
ttk.Label(size_frame, text="正文字号:").pack(side=tk.LEFT)
|
||
self.body_size_var = tk.IntVar(value=self.config.font.body_size)
|
||
ttk.Spinbox(size_frame, from_=12, to=24, textvariable=self.body_size_var, width=5).pack(side=tk.LEFT, padx=5)
|
||
ttk.Label(size_frame, text="px").pack(side=tk.LEFT)
|
||
|
||
# 代码字体
|
||
ttk.Label(font_frame, text="代码字体:").pack(anchor=tk.W, pady=(10, 0))
|
||
self.code_font_var = tk.StringVar(value=self.config.font.code_font)
|
||
ttk.Entry(font_frame, textvariable=self.code_font_var).pack(fill=tk.X, pady=(0, 5))
|
||
|
||
# 行高
|
||
lh_frame = ttk.Frame(font_frame)
|
||
lh_frame.pack(fill=tk.X, pady=5)
|
||
ttk.Label(lh_frame, text="行高:").pack(side=tk.LEFT)
|
||
self.line_height_var = tk.DoubleVar(value=self.config.font.line_height)
|
||
ttk.Spinbox(lh_frame, from_=1.0, to=2.5, increment=0.1, textvariable=self.line_height_var, width=5).pack(side=tk.LEFT, padx=5)
|
||
|
||
# 应用按钮
|
||
ttk.Button(font_frame, text="应用字体设置", command=self.apply_font_settings).pack(fill=tk.X, pady=(10, 0))
|
||
|
||
def create_code_block_settings(self):
|
||
"""创建代码块设置区域"""
|
||
code_frame = ttk.LabelFrame(self.settings_frame, text="代码块设置", padding=10)
|
||
code_frame.pack(fill=tk.X, pady=(0, 10))
|
||
|
||
# 代码主题
|
||
ttk.Label(code_frame, text="代码高亮主题:").pack(anchor=tk.W)
|
||
self.code_theme_var = tk.StringVar(value=self.config.code_block.theme)
|
||
code_themes = self.config.code_block.available_themes
|
||
self.code_theme_combo = ttk.Combobox(
|
||
code_frame,
|
||
values=sorted(code_themes),
|
||
textvariable=self.code_theme_var,
|
||
state="readonly"
|
||
)
|
||
self.code_theme_combo.pack(fill=tk.X, pady=(5, 10))
|
||
self.code_theme_combo.bind("<<ComboboxSelected>>", lambda e: self.apply_code_settings())
|
||
|
||
# 代码块背景色
|
||
bg_frame = ttk.Frame(code_frame)
|
||
bg_frame.pack(fill=tk.X, pady=5)
|
||
ttk.Label(bg_frame, text="代码块背景:").pack(side=tk.LEFT)
|
||
self.code_bg_var = tk.StringVar(value=self.config.code_block.background)
|
||
self.code_bg_btn = tk.Button(
|
||
bg_frame,
|
||
text=" ",
|
||
bg=self.code_bg_var.get(),
|
||
width=3,
|
||
command=self.choose_code_bg
|
||
)
|
||
self.code_bg_btn.pack(side=tk.LEFT, padx=5)
|
||
ttk.Entry(bg_frame, textvariable=self.code_bg_var, width=10).pack(side=tk.LEFT)
|
||
|
||
def create_custom_css_settings(self):
|
||
"""创建自定义 CSS 设置区域"""
|
||
css_frame = ttk.LabelFrame(self.settings_frame, text="自定义 CSS", padding=10)
|
||
css_frame.pack(fill=tk.X, pady=(0, 10))
|
||
|
||
# 已保存的方案选择
|
||
scheme_frame = ttk.Frame(css_frame)
|
||
scheme_frame.pack(fill=tk.X, pady=(0, 10))
|
||
|
||
ttk.Label(scheme_frame, text="已保存的方案:").pack(side=tk.LEFT)
|
||
self.scheme_var = tk.StringVar()
|
||
self.scheme_combo = ttk.Combobox(
|
||
scheme_frame,
|
||
values=self._get_scheme_names(),
|
||
textvariable=self.scheme_var,
|
||
state="readonly",
|
||
width=20
|
||
)
|
||
self.scheme_combo.pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(scheme_frame, text="加载", command=self.load_css_scheme).pack(side=tk.LEFT, padx=2)
|
||
ttk.Button(scheme_frame, text="删除", command=self.delete_css_scheme).pack(side=tk.LEFT, padx=2)
|
||
|
||
# CSS 编辑区
|
||
self.css_text = scrolledtext.ScrolledText(
|
||
css_frame,
|
||
wrap=tk.WORD,
|
||
font=("Consolas", 9),
|
||
height=8
|
||
)
|
||
self.css_text.pack(fill=tk.BOTH, expand=True)
|
||
self.css_text.insert("1.0", self.config.custom_css)
|
||
|
||
# 保存方案区域
|
||
save_frame = ttk.Frame(css_frame)
|
||
save_frame.pack(fill=tk.X, pady=(10, 0))
|
||
|
||
ttk.Label(save_frame, text="方案名称:").pack(side=tk.LEFT)
|
||
self.scheme_name_var = tk.StringVar()
|
||
ttk.Entry(save_frame, textvariable=self.scheme_name_var, width=20).pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(save_frame, text="💾 保存方案", command=self.save_css_scheme).pack(side=tk.LEFT, padx=5)
|
||
|
||
# CSS 按钮
|
||
btn_frame = ttk.Frame(css_frame)
|
||
btn_frame.pack(fill=tk.X, pady=(10, 0))
|
||
|
||
ttk.Button(btn_frame, text="应用 CSS", command=self.apply_custom_css).pack(side=tk.LEFT, padx=(0, 5))
|
||
ttk.Button(btn_frame, text="导出 CSS", command=self.export_css).pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(btn_frame, text="重置", command=self.reset_custom_css).pack(side=tk.LEFT, padx=5)
|
||
|
||
def _get_scheme_names(self):
|
||
"""获取方案名称列表(只显示名称)"""
|
||
schemes = self.editor.theme_manager.get_all_css_schemes()
|
||
return [s.name for s in schemes]
|
||
|
||
def on_theme_change(self, event=None):
|
||
"""主题变更处理"""
|
||
theme_id = self.theme_var.get().split(":")[0]
|
||
self.editor.theme_manager.set_theme(theme_id)
|
||
|
||
# 更新主题色显示
|
||
theme = self.editor.theme_manager.get_current_theme()
|
||
self.primary_color_var.set(theme.primary_color)
|
||
self.color_btn.config(bg=theme.primary_color)
|
||
|
||
self.update_preview()
|
||
|
||
def choose_primary_color(self):
|
||
"""选择主题色"""
|
||
color = colorchooser.askcolor(initialcolor=self.primary_color_var.get())
|
||
if color[1]:
|
||
self.primary_color_var.set(color[1])
|
||
self.color_btn.config(bg=color[1])
|
||
|
||
def apply_primary_color(self):
|
||
"""应用主题色"""
|
||
# 创建自定义主题
|
||
current_theme = self.editor.theme_manager.get_current_theme()
|
||
custom_theme = ThemeConfig(
|
||
id=f"custom_{current_theme.id}",
|
||
name=f"自定义 - {current_theme.name}",
|
||
primary_color=self.primary_color_var.get(),
|
||
background=current_theme.background,
|
||
text_color=current_theme.text_color,
|
||
link_color=current_theme.link_color,
|
||
code_bg=current_theme.code_bg
|
||
)
|
||
self.editor.theme_manager.add_custom_theme(custom_theme)
|
||
self.editor.theme_manager.set_theme(custom_theme.id)
|
||
self.update_preview()
|
||
messagebox.showinfo("成功", "主题色已应用!")
|
||
|
||
def apply_font_settings(self):
|
||
"""应用字体设置"""
|
||
self.editor.update_font_config(
|
||
body_font=self.body_font_var.get(),
|
||
code_font=self.code_font_var.get(),
|
||
body_size=self.body_size_var.get(),
|
||
line_height=self.line_height_var.get()
|
||
)
|
||
self.update_preview()
|
||
messagebox.showinfo("成功", "字体设置已应用!")
|
||
|
||
def choose_code_bg(self):
|
||
"""选择代码块背景色"""
|
||
color = colorchooser.askcolor(initialcolor=self.code_bg_var.get())
|
||
if color[1]:
|
||
self.code_bg_var.set(color[1])
|
||
self.apply_code_settings()
|
||
|
||
def apply_code_settings(self):
|
||
"""应用代码块设置"""
|
||
self.editor.update_code_block_config(
|
||
theme=self.code_theme_var.get(),
|
||
background=self.code_bg_var.get()
|
||
)
|
||
self.update_preview()
|
||
|
||
def apply_custom_css(self):
|
||
"""应用自定义 CSS"""
|
||
css = self.css_text.get("1.0", tk.END).strip()
|
||
self.editor.update_custom_css(css)
|
||
self.update_preview()
|
||
messagebox.showinfo("成功", "自定义 CSS 已应用!")
|
||
|
||
def reset_custom_css(self):
|
||
"""重置自定义 CSS"""
|
||
if messagebox.askyesno("确认", "确定要清空自定义 CSS 吗?"):
|
||
self.css_text.delete("1.0", tk.END)
|
||
self.editor.update_custom_css("")
|
||
self.update_preview()
|
||
|
||
def export_css(self):
|
||
"""导出 CSS 文件"""
|
||
file_path = filedialog.asksaveasfilename(
|
||
defaultextension=".css",
|
||
filetypes=[("CSS 文件", "*.css"), ("所有文件", "*.*")]
|
||
)
|
||
if file_path:
|
||
self.editor.export_css(file_path)
|
||
messagebox.showinfo("成功", f"CSS 已导出到:\n{file_path}")
|
||
|
||
def save_css_scheme(self):
|
||
"""保存 CSS 方案(同名更新,新名添加)"""
|
||
name = self.scheme_name_var.get().strip()
|
||
if not name:
|
||
messagebox.showwarning("提示", "请输入方案名称")
|
||
return
|
||
|
||
css = self.css_text.get("1.0", tk.END).strip()
|
||
|
||
# 收集当前字体和代码块配置
|
||
font_config = {
|
||
"body_font": self.body_font_var.get(),
|
||
"code_font": self.code_font_var.get(),
|
||
"body_size": self.body_size_var.get(),
|
||
"line_height": self.line_height_var.get()
|
||
}
|
||
|
||
code_block_config = {
|
||
"theme": self.code_theme_var.get(),
|
||
"background": self.code_bg_var.get()
|
||
}
|
||
|
||
# 获取当前主题和主题色
|
||
theme_id = self.theme_var.get().split(":")[0] if ":" in self.theme_var.get() else self.theme_var.get()
|
||
primary_color = self.primary_color_var.get()
|
||
|
||
# 检查是否已存在同名方案
|
||
existing_scheme = None
|
||
for scheme in self.editor.theme_manager.get_all_css_schemes():
|
||
if scheme.name == name:
|
||
existing_scheme = scheme
|
||
break
|
||
|
||
from markdown_editor import CssScheme
|
||
|
||
if existing_scheme:
|
||
# 更新现有方案
|
||
if messagebox.askyesno("确认", f"方案 '{name}' 已存在,是否更新?"):
|
||
# 删除旧方案,添加新方案(保留原 ID)
|
||
self.editor.theme_manager.remove_css_scheme(existing_scheme.id)
|
||
updated_scheme = CssScheme(
|
||
id=existing_scheme.id,
|
||
name=name,
|
||
css=css,
|
||
font_config=font_config,
|
||
code_block_config=code_block_config,
|
||
theme_id=theme_id,
|
||
primary_color=primary_color
|
||
)
|
||
self.editor.theme_manager.add_css_scheme(updated_scheme)
|
||
messagebox.showinfo("成功", f"方案 '{name}' 已更新!")
|
||
else:
|
||
return
|
||
else:
|
||
# 添加新方案
|
||
import uuid
|
||
scheme_id = str(uuid.uuid4())[:8]
|
||
scheme = CssScheme(
|
||
id=scheme_id,
|
||
name=name,
|
||
css=css,
|
||
font_config=font_config,
|
||
code_block_config=code_block_config,
|
||
theme_id=theme_id,
|
||
primary_color=primary_color
|
||
)
|
||
self.editor.theme_manager.add_css_scheme(scheme)
|
||
messagebox.showinfo("成功", f"方案 '{name}' 已保存!")
|
||
|
||
# 更新下拉列表
|
||
self.scheme_combo['values'] = self._get_scheme_names()
|
||
self.scheme_name_var.set("")
|
||
|
||
def load_css_scheme(self):
|
||
"""加载 CSS 方案"""
|
||
scheme_name = self.scheme_var.get()
|
||
if not scheme_name:
|
||
messagebox.showwarning("提示", "请选择一个方案")
|
||
return
|
||
|
||
# 根据名称查找方案
|
||
scheme = None
|
||
for s in self.editor.theme_manager.get_all_css_schemes():
|
||
if s.name == scheme_name:
|
||
scheme = s
|
||
break
|
||
|
||
if not scheme:
|
||
messagebox.showerror("错误", "方案不存在")
|
||
return
|
||
|
||
# 应用 CSS
|
||
self.css_text.delete("1.0", tk.END)
|
||
self.css_text.insert("1.0", scheme.css)
|
||
|
||
# 应用字体配置
|
||
if scheme.font_config:
|
||
if "body_font" in scheme.font_config:
|
||
self.body_font_var.set(scheme.font_config["body_font"])
|
||
if "code_font" in scheme.font_config:
|
||
self.code_font_var.set(scheme.font_config["code_font"])
|
||
if "body_size" in scheme.font_config:
|
||
self.body_size_var.set(scheme.font_config["body_size"])
|
||
if "line_height" in scheme.font_config:
|
||
self.line_height_var.set(scheme.font_config["line_height"])
|
||
|
||
# 应用代码块配置
|
||
if scheme.code_block_config:
|
||
if "theme" in scheme.code_block_config:
|
||
self.code_theme_var.set(scheme.code_block_config["theme"])
|
||
if "background" in scheme.code_block_config:
|
||
self.code_bg_var.set(scheme.code_block_config["background"])
|
||
self.code_bg_btn.config(bg=scheme.code_block_config["background"])
|
||
|
||
# 应用主题和主题色
|
||
if scheme.theme_id:
|
||
self.theme_var.set(scheme.theme_id)
|
||
self.editor.theme_manager.set_theme(scheme.theme_id)
|
||
if scheme.primary_color:
|
||
self.primary_color_var.set(scheme.primary_color)
|
||
self.color_btn.config(bg=scheme.primary_color)
|
||
|
||
# 应用所有配置
|
||
self.apply_custom_css()
|
||
self.apply_font_settings()
|
||
self.apply_code_settings()
|
||
|
||
# 将方案名称写入方案名称框
|
||
self.scheme_name_var.set(scheme.name)
|
||
|
||
messagebox.showinfo("成功", f"方案 '{scheme.name}' 已加载!")
|
||
|
||
def delete_css_scheme(self):
|
||
"""删除 CSS 方案"""
|
||
scheme_name = self.scheme_var.get()
|
||
if not scheme_name:
|
||
messagebox.showwarning("提示", "请选择一个方案")
|
||
return
|
||
|
||
# 根据名称查找方案
|
||
scheme = None
|
||
for s in self.editor.theme_manager.get_all_css_schemes():
|
||
if s.name == scheme_name:
|
||
scheme = s
|
||
break
|
||
|
||
if not scheme:
|
||
messagebox.showerror("错误", "方案不存在")
|
||
return
|
||
|
||
if messagebox.askyesno("确认", f"确定要删除方案 '{scheme.name}' 吗?"):
|
||
self.editor.theme_manager.remove_css_scheme(scheme.id)
|
||
self.scheme_combo['values'] = self._get_scheme_names()
|
||
self.scheme_var.set("")
|
||
messagebox.showinfo("成功", "方案已删除!")
|
||
|
||
def update_preview(self):
|
||
"""更新预览"""
|
||
md_content = self.md_text.get("1.0", tk.END)
|
||
html = self.editor.render(md_content)
|
||
|
||
# 更新 HTML 源码显示
|
||
self.html_text.config(state=tk.NORMAL)
|
||
self.html_text.delete("1.0", tk.END)
|
||
self.html_text.insert("1.0", html)
|
||
self.html_text.config(state=tk.DISABLED)
|
||
|
||
# 更新 HTML 渲染预览
|
||
try:
|
||
# 清理 HTML 中的图片 URL,移除反引号和多余空格
|
||
import re
|
||
cleaned_html = html
|
||
# 移除 src 属性中的反引号
|
||
cleaned_html = re.sub(r'src="\s*`\s*([^"`]+)\s*`\s*"', r'src="\1"', cleaned_html)
|
||
cleaned_html = re.sub(r"src='\s*`\s*([^'`]+)\s*`'", r'src="\1"', cleaned_html)
|
||
# 移除 src 属性值前后的空格
|
||
cleaned_html = re.sub(r'src="\s*([^"]+)\s*"', r'src="\1"', cleaned_html)
|
||
|
||
# 使用 tkinterweb 的 HtmlFrame 显示预览
|
||
# 创建临时文件并加载,以便图片能正确显示
|
||
import tempfile
|
||
with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False, encoding='utf-8') as f:
|
||
f.write(cleaned_html)
|
||
temp_path = f.name
|
||
self.html_preview.load_file(temp_path)
|
||
except Exception as e:
|
||
print(f"[Editor] 预览更新失败: {e}")
|
||
|
||
def copy_html(self):
|
||
"""复制 HTML 到剪贴板"""
|
||
html = self.html_text.get("1.0", tk.END)
|
||
self.window.clipboard_clear()
|
||
self.window.clipboard_append(html)
|
||
messagebox.showinfo("成功", "HTML 已复制到剪贴板!")
|
||
|
||
def export_html(self):
|
||
"""导出 HTML 文件"""
|
||
file_path = filedialog.asksaveasfilename(
|
||
defaultextension=".html",
|
||
filetypes=[("HTML 文件", "*.html"), ("所有文件", "*.*")]
|
||
)
|
||
if file_path:
|
||
md_content = self.md_text.get("1.0", tk.END)
|
||
self.editor.export_html(md_content, file_path)
|
||
messagebox.showinfo("成功", f"HTML 已导出到:\n{file_path}")
|
||
|
||
def open_in_browser(self):
|
||
"""在浏览器中打开预览"""
|
||
md_content = self.md_text.get("1.0", tk.END)
|
||
html = self.editor.render(md_content)
|
||
|
||
# 创建临时文件
|
||
with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False, encoding='utf-8') as f:
|
||
f.write(html)
|
||
temp_path = f.name
|
||
|
||
# 在浏览器中打开
|
||
webbrowser.open(f"file://{temp_path}")
|
||
|
||
def run(self):
|
||
"""运行编辑器"""
|
||
if not hasattr(self.window, '_is_main'):
|
||
self.window.mainloop()
|
||
|
||
|
||
def open_editor(parent=None):
|
||
"""打开 Markdown 编辑器窗口"""
|
||
editor = MarkdownEditorWindow(parent)
|
||
return editor
|
||
|
||
|
||
if __name__ == "__main__":
|
||
editor = MarkdownEditorWindow()
|
||
editor.run()
|