Skip to content

Commit dcc5003

Browse files
authored
Merge pull request #38 from weijayboyy/pr37-html-preview
feat(preview): 引入真正渲染的 HTML Markdown 预览(tkhtmlview)
2 parents 70b1827 + ff75b5b commit dcc5003

4 files changed

Lines changed: 72 additions & 1 deletion

File tree

gui.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import tkinter as tk
23
from datetime import datetime
34
from pathlib import Path
@@ -8,6 +9,8 @@
89
from note_model import NoteManager
910
from search_engine import KnowledgeGraph, SearchEngine
1011

12+
logger = logging.getLogger(__name__)
13+
1114

1215
class SmartNotesApp:
1316
def __init__(self, root):
@@ -636,12 +639,43 @@ def _render_markdown_preview(self, content):
636639
pt.insert(tk.END, '\n')
637640
pt.config(state='disabled')
638641

642+
@staticmethod
643+
def _html_preview_widget(parent):
644+
"""Return an HTMLScrolledText if tkhtmlview is installed, else None."""
645+
try:
646+
from tkhtmlview import HTMLScrolledText # noqa: PLC0415
647+
except ImportError:
648+
return None
649+
return HTMLScrolledText(parent, html="")
650+
639651
def toggle_preview(self):
652+
content = self.editor_text.get(1.0, tk.END)
653+
# If an HTML preview backend is available, prefer it; otherwise fall
654+
# back to the Tk-tag renderer introduced earlier.
655+
if getattr(self, '_html_preview', 'unset') == 'unset':
656+
self._html_preview = self._html_preview_widget(self.editor_text.master)
657+
658+
if self._html_preview is not None:
659+
if self._html_preview.winfo_ismapped():
660+
self._html_preview.pack_forget()
661+
self.editor_text.pack(fill='both', expand=True)
662+
else:
663+
self.editor_text.pack_forget()
664+
self._html_preview.pack(fill='both', expand=True)
665+
try:
666+
html = self.markdown_parser.parse_to_styled_html(content)
667+
self._html_preview.set_html(html)
668+
except Exception:
669+
logger.exception("HTML 预览渲染失败,回退到标签渲染")
670+
self._html_preview.pack_forget()
671+
self.preview_text.pack(fill='both', expand=True)
672+
self._render_markdown_preview(content)
673+
return
674+
640675
if self.preview_text.winfo_ismapped():
641676
self.preview_text.pack_forget()
642677
self.editor_text.pack(fill='both', expand=True)
643678
else:
644-
content = self.editor_text.get(1.0, tk.END)
645679
self.editor_text.pack_forget()
646680
self.preview_text.pack(fill='both', expand=True)
647681
self._render_markdown_preview(content)

markdown_parser.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,19 @@ def parse_to_html(self, text: str) -> str:
5252
logger.exception("Markdown解析失败: %s", e)
5353
return f"<pre>{text}</pre>"
5454

55+
_PREVIEW_CSS = (
56+
"body{font-family:'Microsoft YaHei UI',sans-serif;font-size:14px;line-height:1.6;}"
57+
"h1,h2,h3{color:#2d3748;}code{background:#f1f1f4;padding:2px 4px;border-radius:3px;}"
58+
"pre{background:#2d2d2d;color:#f8f8f2;padding:10px;border-radius:5px;overflow:auto;}"
59+
"table{border-collapse:collapse;}td,th{border:1px solid #ccc;padding:4px 8px;}"
60+
"blockquote{border-left:4px solid #cbd5e0;margin:0;padding-left:12px;color:#718096;}"
61+
)
62+
63+
def parse_to_styled_html(self, text: str) -> str:
64+
"""Full HTML document with embedded CSS for an HTML preview widget."""
65+
body = self.parse_to_html(text)
66+
return f"<html><head><style>{self._PREVIEW_CSS}</style></head><body>{body}</body></html>"
67+
5568
def extract_headings(self, text: str) -> List[Tuple[int, str]]:
5669
headings = []
5770
for match in self.heading_pattern.finditer(text):

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ dependencies = [
1414
"pillow>=10.0,<13",
1515
]
1616

17+
[project.optional-dependencies]
18+
# Richer HTML Markdown preview; the app falls back to a tag renderer without it.
19+
preview = ["tkhtmlview>=0.2"]
20+
1721
[dependency-groups]
1822
dev = [
1923
"ruff>=0.5",

tests/test_styled_html.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""Styled HTML preview output + graceful absence of tkhtmlview."""
2+
import importlib.util
3+
4+
from markdown_parser import MarkdownParser
5+
6+
7+
def test_styled_html_wraps_with_css():
8+
mp = MarkdownParser()
9+
html = mp.parse_to_styled_html("# Title\n\n**bold**")
10+
assert html.startswith("<html>")
11+
assert "<style>" in html and "font-family" in html
12+
assert "<h1" in html # heading rendered to HTML, not raw markdown
13+
assert "<strong>bold</strong>" in html
14+
15+
16+
def test_tkhtmlview_optional():
17+
# The app must work whether or not tkhtmlview is installed; just assert the
18+
# import-capability check is well-defined (no exception).
19+
available = importlib.util.find_spec("tkhtmlview") is not None
20+
assert available in (True, False)

0 commit comments

Comments
 (0)