diff --git a/TODO.md b/TODO.md
index 9936447..ca9c37e 100644
--- a/TODO.md
+++ b/TODO.md
@@ -21,12 +21,21 @@
- **Слова внутри списков и цитат остаются кликабельными** (интерактивный клик-перевод работает!).
- [x] **Заголовки Markdown (`##`, `###`)**:
- Конвертация в `
`, `
` с удалением сырых решёток `#`.
+- [x] **Таблицы Markdown (`| col | col |`)**:
+ - Адаптивные таблицы с рамками, стилизацией заголовков ``, зебра-подсветкой строк и горизонтальным скроллом на мобильных.
+ - Каждая ячейка поддерживает интерактивные слова для изучения и инлайн-код.
+- [x] **HTML-блоки и теги в Markdown (`
`, `
`, ``, ``, ``, ` `)**:
+ - Нативный парсинг через встроенный `DOMParser` без внешних библиотек.
+ - Устранена утечка сырых тегов (``, ``, `
`) в словарные слова.
+- [x] **Ссылки и изображения (`[text](url)`, ``, ``)**:
+ - Поддержка бейджей shields.io, тренд-графиков и внешних картинок.
+ - Автоматическое скрытие битых/отсутствующих локальных путей (`assets/...`) через обработчик `onerror`.
- [x] **Точное позиционирование окна перевода у курсора/слова (Popup Positioning)**:
- Исправлен баг двойного смещения координат: убран `window.scrollY` при `position: fixed`.
- - Окно позиционируется точно под кликнутым словом (`rect.bottom + 8`) либо над ним (`rect.top - popupHeight - 8`), если снизу недостаточно места.
- - Окно центрируется относительно кликнутого слова с проверкой границ вьюпорта (отступы минимум 12px от краёв).
+ - Окно перенесено в корень `
` для исключения искажений контекста позиционирования.
+ - Плавное отслеживание скролла через `requestAnimationFrame` с автозакрытием при уходе слова за границы экрана или под липкую шапку.
- [x] **Стилизация в CSS ([`app/static/css/style.css`](file:///home/ai-projects/services/study.qpid.pro/app/static/css/style.css))**:
- - Добавлены стили для `.reader-content-body pre`, `code`, `blockquote`, `ul`, `ol`, `.reader-code-block`, `.reader-inline-code`.
+ - Добавлены стили для `.reader-content-body pre`, `code`, `blockquote`, `ul`, `ol`, `.reader-table`, `.reader-heading`, `.reader-link`, `.reader-img`.
---
diff --git a/app/reader.py b/app/reader.py
index e73106f..ff6930e 100644
--- a/app/reader.py
+++ b/app/reader.py
@@ -294,8 +294,8 @@ def parse_text(content: str, title: str = "Untitled", author: str = "") -> Tuple
lines = content.split("\n")
first_line = lines[0].strip()
- # Extract title from first line if default
- if title == "Untitled":
+ # Extract title from first line or # Heading / if default or generic README
+ if title == "Untitled" or title.lower().startswith("readme"):
if first_line.startswith("# "):
title = first_line.lstrip("# ").strip()
if len(lines) > 1:
@@ -304,6 +304,12 @@ def parse_text(content: str, title: str = "Untitled", author: str = "") -> Tuple
title = first_line.split(":", 1)[1].strip()
if len(lines) > 1:
content = "\n".join(lines[1:]).strip()
+ else:
+ h1_match = re.search(r"