[2026-08-24] eagle: personal/projects/personal-os/eagle-dashboard.md personal/tech/bilety-oxota-interaktiv.md
This commit is contained in:
@@ -314,6 +314,7 @@ When a launchd cron is created and `launchctl print gui/<uid>/<label>` shows `la
|
||||
|
||||
## History
|
||||
|
||||
|- 2026-08-24: **bilety.html added to Pages.** Deployed interactive hunting-license exam page (`/Library/WebServer/Documents/bilety.html`, ~894 KB). 176 question cards, 532 answer buttons. All markup embedded in the HTML: each answer is a `<button class="whale-a" data-correct="N" data-n="M" onclick="...">`, correct-answer number hidden from display (lives only in `data-correct`), inline onclick colors wrong=red / correct=green. No JS-builder script — everything static. Built from `~/Downloads/origen_raw.html` via `~/Downloads/gen2.py`. Pitfalls learned: (1) `_read_page_metadata()` reads files live — no dashboard restart needed; (2) webroot is root-owned, `sudo cp` needed unless perms loosened; (3) relative `href`/`src` deps must sit in webroot too.
|
||||
|- 2026-06-25 (round 10): **Launchd status — PID/exit code from launchctl list.** Added PID parsing (next-format "PID" = N) and LastExitStatus parsing in `_list_launchd_crons()`. `last_status` is None for running processes, integer exit code otherwise. Frontend shows `· exit <code>` or `· never run`. Legacy tab-separated format no longer supported. macOS launchctl returns next-format plist (not JSON) — regex parsing, not json.loads.
|
||||
|- 2026-06-25 (round 9): **RunAtLoad checkbox for launchd crons.** Added `RunAtLoad` field to launchd plist creation. Backend: `main.py` line 636 changed from `pd["RunAtLoad"] = False` to `pd["RunAtLoad"] = body.get("run_at_load", False)`. Frontend: checkbox "Run on load" in cron modal, shown only for source=launchd via `x-show`; `openAddCron()`, `openCronEdit()`, `saveCronModal()` all wired. Backend `_list_launchd_crons()` returns `run_at_load` for edit modal. **Also fixed:** `_msg` auto-dismiss bug — stale object reference after `fetchCrons()` replaced with `find` in fresh array (both crons and services).
|
||||
- 2026-06-25 (round 7): **Launchd schedule — source-dependent validation.** `validateSchedule()` now takes `source` param. For `launchd`: only `every Ns/m/h/d` accepted; cron and ISO rejected with explicit message. For Eagle/Whale: all three formats. Hints, placeholders, and `prettySchedule()` also differ by source. `Query` import added to FastAPI.
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
created: 2026-08-24
|
||||
tags: [html, quiz, tilda, static-page, python, eagle-dash]
|
||||
updated: 2026-08-24
|
||||
---
|
||||
|
||||
# Билеты охотника — статичный интерактив (a1-tir.ru/bilety → bilety.html)
|
||||
|
||||
## Итог (2026-08-24)
|
||||
|
||||
Скачал `https://a1-tir.ru/bilety` (билеты для экзамена на гражданское оружие/охотничий минимум) и превратил в **статичную страницу с кнопками-ответами, вшитыми в HTML** (БЕЗ JS-скрипта-генератора). Задеплоено в Eagle Dashboard Pages: `/Library/WebServer/Documents/bilety.html` (≈894 КБ), URL `/local-pages/bilety.html`, вкладка Pages.
|
||||
|
||||
- **176 карточек-вопросов, 532 кнопки-ответа** (из ~180 исходных; потеряны лишь некоторые многострочные вопросы, где `<strong>`-фрагмент без номера).
|
||||
- Каждый ответ — `<button class="whale-a" data-correct="N" data-n="M" onclick="...">`.
|
||||
- Номер правильного ответа **скрыт** — живёт только в `data-correct`, не рендерится на экран.
|
||||
- Клик (инлайн `onclick` в каждой кнопке): выбрал неверно → кнопка красная `wrong` + правильная подсвечивается зелёной `correct`; верно → зелёная.
|
||||
- CSS (`.whale-answers`, `.whale-a`, `.correct`, `.wrong`) вшит в `<head>` страницы.
|
||||
|
||||
## Универсальная структура исходника (Tilda text-блок)
|
||||
|
||||
Одна строка-контейнер на раздел, `div field="text" class="t-text t-text_md ">` (4 таких блока на странице). Внутри:
|
||||
|
||||
```
|
||||
<p style="text-align:center;"><strong>Заголовок раздела</strong></p> (не всегда)
|
||||
<strong>1.1. Текст вопроса</strong>
|
||||
<br/>1. Вариант1
|
||||
<br/>2. Вариант2
|
||||
<br/>3. Вариант3
|
||||
<br/><em>2</em> ← номер правильного (скрывать)
|
||||
<br/><strong> </strong><br/>
|
||||
<strong>1.2. ...</strong>
|
||||
...
|
||||
```
|
||||
|
||||
- Вопрос может занимать несколько подряд идущих `<strong>` (фрагмент без номера — продолжение).
|
||||
- Ответы — текстовые узлы после `</strong>` между `<br/>`, каждый начинается с `N. `.
|
||||
- `<em>N</em>` — чистый номер правильного. Исключение: `<em><u>Примечание</u>: …</em>` — это примечание, не номер.
|
||||
- Мусор: `<strong> </strong>`, `<em> </em>`, пустые `<em>`/`<strong data-redactor-tag="strong">`.
|
||||
|
||||
## Генератор: `~/Downloads/gen2.py`
|
||||
|
||||
Python (без внешних зависимостей), превращает `origen_raw.html` → `bilety.html`.
|
||||
|
||||
```bash
|
||||
python3 gen2.py # вывод: containers=4 cards=176 buttons=532 bytes=816979
|
||||
```
|
||||
|
||||
Ключевые моменты:
|
||||
- `find_end(html, start)` — баланс `<div>`/`</div>` от индекса открывающего `<div field="text">` до его закрытия.
|
||||
- `transform_container` — **сначала вырезает `<p[^>]*>.*?</p>`** (заголовки разделов). Это критично: без этого блоки с `<p>`-заголовком (раздел «Правовая подготовка» и «Огневая подготовка») давали 0 карточек, вопросы терялись целиком. Убирание заголовков заставляет обрабатывать каждый `<strong>` единообразно (как в блоках без заголовка).
|
||||
- `_scan(inner)` — токенизация текста/тегов; каждый `N. `-ответ → `cur["a"]`; `<em>`-цифра → `cur["c"]` (правильный); strong без номера при пустых ответах → мержится в текущий вопрос.
|
||||
- `_btn(num, text, correct)` — инлайн `onclick`, красящий красный/зелёный.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **Блоки с `<p>`-заголовком**: если не вырезать `<p>.../p>` первым шагом, такие блоки дают 0 карточек (вопросы теряются). Симптом: в собранном файле `cards` вдвое меньше (~86 вместо ~180).
|
||||
2. **Скрытие номера**: не выводить `<em>` в разметку — только `data-correct` на кнопке.
|
||||
3. **`grep -c` по однострочному HTML** вернёт 1 (файл — одна гигантская строка), даже если совпадений сотни. Считать через `python` (`s.count(...)`), не через grep.
|
||||
4. **Файл после генерации большой** (~894 КБ): на каждую кнопку инлайн-`onclick` — приемлемо для статики.
|
||||
5. Node парсеры/`node --max-old-space-size` тут НЕ нужны — на больших минифицированных Tilda-файлах node легко падает в OOM (heap limit) при самопальных токенизаторах. Python + `str.find`/`regex` надёжнее и не жрёт память.
|
||||
|
||||
## Деплой в Eagle Dashboard Pages
|
||||
|
||||
- Механика + права — в `personal/projects/personal-os/eagle-dashboard.md` (секция «Как добавить страницу в Pages»).
|
||||
- Скопировать `bilety.html` в `/Library/WebServer/Documents/` (root-owned, нужен sudo) → страница подхватывается живьём, рестарт дашборда не нужен.
|
||||
Reference in New Issue
Block a user