'use strict'; /* assistant.js — «Квантик-ассистент»: плавающий компаньон на всех страницах. * Подсказки по контексту + проактивные напоминания + поздравления + «Спроси». * Правиловый движок (без модели). Состояние «видел» — на сервере (assistant_seen), * дневной лимит/детект событий — в localStorage. Лицо = pet-sprite.js, данные — * /api/assistant/context и /api/pet. Гейт фичи 'pet' проверяется на сервере. * Грузится через sidebar.js (app-страницы) и серверный inject (учебник). */ (function () { if (window.__assistantBooted) return; window.__assistantBooted = true; if (window.parent !== window) return; // не в iframe/embed if (!window.LS || !LS.getToken || !LS.getToken()) return; // только залогиненным var DAILY_CAP = 2; // консервативно: не больше 2 авто-подсказок в день var AUTO_DELAY = 7000; // показать подсказку через 7с на странице var reduceMotion = window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches; var SRV = null, PET = null, picked = null, root = null, bubble = null, openState = false; /* ── helpers ─────────────────────────────────────────────────────────── */ function esc(s) { return (window.LS && LS.escapeHtml) ? LS.escapeHtml(String(s == null ? '' : s)) : String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]; }); } function lsGet(k) { try { return localStorage.getItem(k); } catch (e) { return null; } } function lsSet(k, v) { try { localStorage.setItem(k, v); } catch (e) {} } function todayKey() { return new Date().toISOString().slice(0, 10); } function pageId() { var p = location.pathname.replace(/\/+$/, '') || '/'; if (p === '/' || p === '/dashboard') return 'dashboard'; if (p.indexOf('/textbook') === 0) return 'textbook'; if (p === '/classroom') return 'classroom'; if (p === '/board') return 'board'; if (p.indexOf('/exam') === 0) return 'exam'; if (p === '/flashcards') return 'flashcards'; if (p === '/my-materials') return 'materials'; if (p === '/lab') return 'lab'; if (p === '/theory' || p.indexOf('/course') === 0 || p.indexOf('/lesson') === 0) return 'theory'; if (p === '/textbooks') return 'textbooks'; if (p === '/library') return 'library'; if (p === '/knowledge-map') return 'knowledge'; if (p === '/biochem') return 'biochem'; if (p === '/red-book') return 'redbook'; if (p === '/crossword') return 'crossword'; if (p === '/hangman') return 'hangman'; if (p === '/collection') return 'collection'; if (p === '/analytics') return 'analytics'; if (p === '/gradebook') return 'gradebook'; if (p === '/lesson-history') return 'lessonhistory'; if (p === '/question-bank') return 'qbank'; if (p === '/classes') return 'classes'; if (p === '/homework') return 'homework'; if (p === '/pet') return 'pet'; return 'other'; } var PAGE = pageId(); var SUPPRESS_PAGE = (PAGE === 'classroom'); // не мешаем на живом уроке function quest(undoneOnly) { var qs = (PET && PET.quests) || []; for (var i = 0; i < qs.length; i++) if (!undoneOnly || !qs[i].done) return qs[i]; return null; } function activeToday() { if (!PET) return true; if (PET.daysSinceLogin === 0) { var q = (PET.quests || []).find ? (PET.quests || []).find(function (x) { return x.id === 'xp30'; }) : null; return q ? (q.progress || 0) > 0 : true; // есть прогресс сегодня } return false; } /* ── каталог правил ──────────────────────────────────────────────────── */ // scope: page | proactive | celebration. when(C) → bool. action(C) → {label,url}|null. var RULES = [ // — контекстные подсказки генерируются из PAGE_HINTS ниже — // — проактивные (из реальных данных) — { id: 'hw-overdue', scope: 'proactive', cooldownDays: 1, maxShows: 30, when: function () { return !!(SRV && SRV.homework && SRV.homework.overdue); }, text: function () { return 'Просрочена домашка: «' + (SRV.homework.overdue.title || 'задание') + '». Загляни в раздел.'; }, action: function () { return { label: 'К домашке', url: '/homework' }; } }, { id: 'hw-soon', scope: 'proactive', cooldownDays: 1, maxShows: 30, when: function () { return !!(SRV && SRV.homework && SRV.homework.dueSoon); }, text: function () { return 'Скоро дедлайн: «' + (SRV.homework.dueSoon.title || 'задание') + '».'; }, action: function () { return { label: 'К домашке', url: '/homework' }; } }, { id: 'lesson-continue', scope: 'proactive', cooldownDays: 1, maxShows: 60, when: function () { return !!(SRV && SRV.activeLesson && SRV.activeLesson.courseId); }, text: function () { return 'Продолжи: «' + (SRV.activeLesson.courseTitle || SRV.activeLesson.lessonTitle || 'урок') + '».'; }, action: function () { return { label: 'Продолжить', url: '/course?id=' + SRV.activeLesson.courseId }; } }, { id: 'cards-due', scope: 'proactive', cooldownDays: 1, maxShows: 60, when: function () { return !!(SRV && SRV.dueCards > 0); }, text: function () { return 'К повторению ' + SRV.dueCards + ' ' + plural(SRV.dueCards, 'карточка', 'карточки', 'карточек') + ' — освежим память?'; }, action: function () { return { label: 'Повторить', url: '/flashcards' }; } }, { id: 'streak-risk', scope: 'proactive', cooldownDays: 1, maxShows: 60, when: function () { return !!(PET && PET.streakCurrent >= 1 && !activeToday() && new Date().getHours() >= 18); }, text: function () { return 'Серия ' + PET.streakCurrent + ' ' + plural(PET.streakCurrent, 'день', 'дня', 'дней') + ' под угрозой — позанимайся сегодня, чтобы не потерять.'; }, action: function () { return { label: 'Заниматься', url: '/exam-prep/math9' }; } }, { id: 'quest', scope: 'proactive', cooldownDays: 1, maxShows: 90, when: function () { return !!(quest(true) && new Date().getHours() >= 16); }, text: function () { var q = quest(true); return 'Остался квест дня: «' + (q.label || 'задание') + '».'; }, action: function () { return null; } }, { id: 'weak-subject', scope: 'proactive', cooldownDays: 2, maxShows: 40, when: function () { return !!(SRV && SRV.weakSubject); }, text: function () { var w = SRV.weakSubject; return 'Слабее всего идёт ' + (w.name || 'предмет') + ' (' + w.avg + '%). Потренируемся?'; }, action: function () { return { label: 'Потренироваться', url: '/exam-prep/math9' }; } }, { id: 'brief', scope: 'proactive', cooldownDays: 1, maxShows: 300, when: function () { return PAGE === 'dashboard' && new Date().getHours() < 12; }, text: function () { var plan = dailyPlan(), days = activeDaysThisWeek(); var s = 'Доброе утро! ' + (days != null ? 'На этой неделе ты занимался ' + days + ' из 5 дн. ' : ''); return s + (plan.length ? 'Сегодня: ' + plan.join(', ') + '.' : 'Сегодня можно начать с короткого теста.'); }, action: function () { return dailyPlanAction(); } }, { id: 'daily-plan', scope: 'proactive', cooldownDays: 1, maxShows: 120, when: function () { return PAGE === 'dashboard' && new Date().getHours() >= 12 && dailyPlan().length > 0; }, text: function () { return 'План на сегодня: ' + dailyPlan().join(', ') + '. Начнём?'; }, action: function () { var p = dailyPlanAction(); return p; } }, ]; /* План на сегодня — из дневных квестов и карточек к повторению */ function dailyPlan() { var out = []; var qs = (PET && PET.quests) || []; qs.forEach(function (q) { if (!q.done && q.label) out.push(q.label.toLowerCase()); }); if (SRV && SRV.dueCards > 0) out.push('повторить ' + SRV.dueCards + ' ' + plural(SRV.dueCards, 'карточку', 'карточки', 'карточек')); return out.slice(0, 3); } function dailyPlanAction() { if (SRV && SRV.dueCards > 0) return { label: 'Повторить карточки', url: '/flashcards' }; return { label: 'К занятиям', url: '/exam-prep/math9' }; } function activeDaysThisWeek() { try { var w = (PET && PET.weeklyXP) || []; return w.length ? w.filter(function (d) { return (d.xp || 0) > 0; }).length : null; } catch (e) { return null; } } function plural(n, one, few, many) { var m10 = n % 10, m100 = n % 100; if (m10 === 1 && m100 !== 11) return one; if (m10 >= 2 && m10 <= 4 && (m100 < 10 || m100 >= 20)) return few; return many; } /* ── контентные подсказки по страницам (генерируются в RULES) ────────── */ var PAGE_HINTS = { textbook: ['Любой кусок страницы можно вырезать картинкой в «Мои материалы» — кнопка «Вырезать область» внизу.', 'Ссылка из адресной строки ведёт прямо на этот параграф — удобно делиться.'], exam: ['Три режима: экзамен (как на ЦТ/ЦЭ), тренировка (с разбором) и случайный.', 'После теста доступен разбор: правильные ответы и решения.'], flashcards: ['Формулы вводятся через KaTeX-палитру, можно добавить и картинку.', 'Система сама напоминает, какие карточки пора повторить.'], materials: ['Раскладывай материалы по папкам, а поверх фото можно рисовать — кнопка с карандашом.', 'Клик по картинке открывает её в просмотрщике прямо на странице.'], lab: ['Симуляции запускаются прямо в браузере — ничего ставить не нужно.', 'У оптической скамьи есть режим «Конструктор» — собирай системы из линз и зеркал.'], dashboard: ['Виджеты на дашборде можно включать и переставлять под себя.', 'Блок «Активность» показывает серию и занятия за недели.'], textbooks: ['Внутри учебника — главы и параграфы; прочитанное отмечается и идёт в прогресс.'], library: ['В библиотеке — файлы и материалы от учителя; ищи по предмету.'], knowledge: ['Карта знаний показывает связи тем — видно, что уже освоено.'], biochem: ['Интерактивные модели молекул и реакций — крути и разбирай.'], redbook: ['Красная книга — карточки видов; листай и запоминай.'], crossword: ['Кроссворд по терминам предмета — отгадывай и получай XP.'], hangman: ['«Виселица» по терминам — угадывай слово по буквам.'], collection: ['Коллекция — твои собранные награды и предметы.'], board: ['Инструмент «выделение» двигает и поворачивает объекты; есть фигуры, формулы и линейка.', 'Страницу доски можно сохранить в «Мои материалы».'], lessonhistory: ['Архив онлайн-уроков — доска и заметки сохраняются после занятия.'], qbank: ['Банк вопросов — основа для тестов и заданий.'], theory: ['Можно создать одиночный «Быстрый урок» без курса.', 'Прогресс по урокам отслеживается — продолжай с того же места.'], classes: ['Создавай классы и выдавай задания; ученики входят по коду.'], analytics: ['Аналитика: динамика результатов и слабые темы.'], gradebook: ['Журнал — оценки и сдачи по заданиям класса.'], homework: ['Здесь все задания и дедлайны; работу можно загрузить прямо тут.'], pet: ['Гладь и корми Квантика, закрывай квесты — за это XP и монеты.'], }; Object.keys(PAGE_HINTS).forEach(function (pg) { PAGE_HINTS[pg].forEach(function (text, idx) { RULES.push({ id: 'p-' + pg + '-' + idx, scope: 'page', cooldownDays: 12, maxShows: 2, when: (function (p) { return function () { return PAGE === p; }; })(pg), text: (function (t) { return function () { return t; }; })(text), action: function () { return null; }, }); }); }); /* ── «Совет дня» — ротация фич-открытий (дашборд) ────────────────────── */ var TIPS = [ 'фрагмент учебника можно сохранить картинкой в «Мои материалы».', 'в «Теории» есть «Быстрый урок» — один урок без создания курса.', 'поверх сохранённого фото можно рисовать пометки.', 'формулы в карточках и на доске вводятся через KaTeX.', 'серия за ежедневные занятия делает Квантика счастливее.', 'поиск по платформе — Ctrl+K: уроки, курсы, файлы и вопросы.', 'заметку из «Мои материалы» можно превратить во флешкарту.', 'тёмную тему и звуки включишь в Профиль → Настройки.', ]; RULES.push({ id: 'tip-daily', scope: 'page', cooldownDays: 1, maxShows: 200, when: function () { return PAGE === 'dashboard'; }, text: function () { return 'А ты знал: ' + TIPS[(new Date().getDate()) % TIPS.length]; }, action: function () { return null; }, }); /* ── выбор подсказки ─────────────────────────────────────────────────── */ function eligible(rule) { if (SUPPRESS_PAGE && rule.scope !== 'celebration') return false; var s = (SRV && SRV.seen && SRV.seen[rule.id]) || null; if (s && s.dismissed) return false; if (s && rule.maxShows && s.count >= rule.maxShows) return false; if (s && s.lastAt && rule.cooldownDays) { var days = (Date.now() - Date.parse(s.lastAt + 'Z')) / 86400000; if (days < rule.cooldownDays) return false; } try { return !!rule.when(); } catch (e) { return false; } } function pickRule() { var order = { celebration: 3, proactive: 2, page: 1 }; var cands = RULES.filter(eligible).sort(function (a, b) { return (order[b.scope] || 0) - (order[a.scope] || 0); }); return cands[0] || null; } /* ── поздравления (детект по дельте, localStorage) ───────────────────── */ function celebration() { if (!PET) return null; var lvl = PET.petLevel || 1; var prevLvl = parseInt(lsGet('asst_lvl') || '', 10); if (!isNaN(prevLvl) && lvl > prevLvl) { lsSet('asst_lvl', String(lvl)); return { id: 'cel-level', mood: 'ecstatic', text: 'Ура! ' + (PET.petName || 'Квантик') + ' дорос до уровня ' + lvl + '! Так держать.' }; } if (isNaN(prevLvl)) lsSet('asst_lvl', String(lvl)); var ms = [3, 7, 14, 30, 60, 100]; var cur = PET.streakCurrent || 0; var prevMs = parseInt(lsGet('asst_streak_ms') || '0', 10) || 0; var hit = 0; for (var i = 0; i < ms.length; i++) if (cur >= ms[i]) hit = ms[i]; if (hit > prevMs) { lsSet('asst_streak_ms', String(hit)); return { id: 'cel-streak', mood: 'ecstatic', text: 'Серия ' + hit + ' ' + plural(hit, 'день', 'дня', 'дней') + ' подряд! Огонь.' }; } if (hit > 0 && prevMs === 0) lsSet('asst_streak_ms', String(hit)); return null; } /* ── дневной лимит авто-показов ──────────────────────────────────────── */ function dayCount() { if (lsGet('asst_day') !== todayKey()) { lsSet('asst_day', todayKey()); lsSet('asst_day_n', '0'); } return parseInt(lsGet('asst_day_n') || '0', 10) || 0; } function bumpDay() { lsSet('asst_day', todayKey()); lsSet('asst_day_n', String(dayCount() + 1)); } /* ── PetSprite ───────────────────────────────────────────────────────── */ function ensurePet(cb) { if (window.PetSprite && PetSprite.render) return cb(); var s = document.createElement('script'); s.src = '/js/pet-sprite.js'; s.onload = cb; s.onerror = cb; document.head.appendChild(s); } function faceSVG(mood) { try { if (window.PetSprite && PetSprite.render) { return PetSprite.render(PET ? (PET.petLevel || 1) : 1, mood || (PET && PET.mood) || 'happy', (PET && PET.accessories) || [], (PET && PET.petColor) || 'purple', (PET && PET.streakCurrent) || 0); } } catch (e) {} return ''; } /* ── styles ──────────────────────────────────────────────────────────── */ function ensureStyles() { if (document.getElementById('asst-style')) return; var s = document.createElement('style'); s.id = 'asst-style'; s.textContent = [ '.asst-root{position:fixed;left:18px;bottom:18px;z-index:8500;font-family:Manrope,system-ui,sans-serif;transition:left .28s cubic-bezier(.4,0,.2,1);}', // в приложении сдвигаем правее сайдбара (230/62px), чтобы не перекрывать профиль в сайдбаре '.app-layout ~ .asst-root{left:248px;}', '.app-layout.sb-collapsed ~ .asst-root{left:80px;}', '.asst-fab{width:54px;height:54px;border-radius:50%;border:none;background:#fff;cursor:pointer;padding:4px;', ' box-shadow:0 8px 24px rgba(139,92,246,.32);transition:transform .15s;position:relative;display:block;}', '.asst-fab:hover{transform:translateY(-2px) scale(1.04);}', '.asst-fab svg{width:100%;height:100%;display:block;}', '.asst-dot{position:absolute;top:0;right:0;width:13px;height:13px;border-radius:50%;background:#F15BB5;border:2px solid #fff;}', reduceMotion ? '' : '.asst-fab.pulse{animation:asstPulse 2.2s ease-in-out infinite;}', '@keyframes asstPulse{0%,100%{box-shadow:0 8px 24px rgba(139,92,246,.32);}50%{box-shadow:0 8px 30px rgba(241,91,181,.5);}}', '.asst-bubble{position:absolute;left:0;bottom:66px;width:380px;max-width:92vw;background:#fff;border-radius:18px;', ' box-shadow:0 20px 56px rgba(15,23,42,.24);padding:15px 17px;border:1px solid rgba(15,23,42,.07);', ' opacity:0;transform:translateY(8px) scale(.98);pointer-events:none;transition:opacity .18s,transform .18s;transform-origin:bottom left;}', '.asst-name-face{display:inline-block;width:20px;height:20px;vertical-align:-4px;margin-right:7px;}', '.asst-name-face svg{width:100%;height:100%;display:block;}', '.asst-memnote{font-size:.66rem;color:#9aa5b4;margin-top:9px;line-height:1.45;border-top:1px solid rgba(15,23,42,.05);padding-top:8px;}', '.asst-bubble.open{opacity:1;transform:translateY(0);pointer-events:auto;}', '.asst-x{position:absolute;top:8px;right:8px;width:26px;height:26px;border:none;background:transparent;color:#8a94a6;', ' cursor:pointer;border-radius:7px;font-size:18px;line-height:1;}', '.asst-x:hover{background:rgba(15,23,42,.06);color:#0F172A;}', '.asst-name{font-size:.7rem;font-weight:800;color:#9B5DE5;text-transform:uppercase;letter-spacing:.03em;margin-bottom:6px;}', '.asst-text{font-size:.86rem;line-height:1.5;color:#28324a;margin-bottom:12px;white-space:pre-line;}', '.asst-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}', '.asst-btn{display:inline-flex;align-items:center;gap:6px;padding:7px 13px;border-radius:99px;border:none;cursor:pointer;', ' font:700 .78rem Manrope,sans-serif;background:#9B5DE5;color:#fff;text-decoration:none;}', '.asst-btn:hover{background:#7e3eca;}', '.asst-link{background:none;border:none;color:#8a94a6;cursor:pointer;font:600 .76rem Manrope,sans-serif;padding:4px 2px;text-decoration:none;}', '.asst-link:hover{color:#9B5DE5;}', '.asst-ask-in{width:100%;box-sizing:border-box;padding:9px 12px;border:1px solid #e2e8f0;border-radius:10px;font:inherit;font-size:.84rem;margin-bottom:10px;}', '.asst-ans{font-size:.82rem;line-height:1.5;color:#28324a;border-top:1px solid rgba(15,23,42,.06);padding:9px 0;}', '.asst-ans:first-of-type{border-top:none;}', '.asst-ans-q{font-weight:700;color:#0F172A;margin-bottom:2px;}', '.asst-ans-link{display:inline-block;margin-top:4px;color:#9B5DE5;font-weight:700;font-size:.78rem;text-decoration:none;}', '.asst-ans-sec{font-size:.66rem;font-weight:800;color:#8a94a6;text-transform:uppercase;letter-spacing:.03em;margin:12px 0 2px;}', '.asst-ans-box{max-height:46vh;overflow:auto;}', '.asst-chips{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px;}', '.asst-chip{border:1px solid #e2e8f0;background:#f8fafc;border-radius:99px;padding:5px 10px;font:600 .72rem Manrope,sans-serif;color:#475569;cursor:pointer;text-align:left;}', '.asst-chip:hover{border-color:#9B5DE5;color:#9B5DE5;}', '.asst-chip-ctx{background:rgba(155,93,229,.1);border-color:rgba(155,93,229,.35);color:#7e3eca;}', '.asst-rich{font-size:.84rem;line-height:1.55;color:#28324a;}', '.asst-rich>div{margin:3px 0;}', '.asst-rich ul,.asst-rich ol{margin:4px 0 4px 18px;padding:0;}', '.asst-rich li{margin:2px 0;}', '.asst-rich code{background:rgba(15,23,42,.06);border-radius:4px;padding:1px 4px;}', // длинные формулы не помещаются в узкий блок → горизонтальная прокрутка, ничего не обрезается '.asst-rich{overflow-wrap:anywhere;}', '.asst-rich .katex-display{margin:6px 0;overflow-x:auto;overflow-y:hidden;padding-bottom:4px;max-width:100%;}', '.asst-rich .katex-display::-webkit-scrollbar{height:6px;}', '.asst-rich .katex-display::-webkit-scrollbar-thumb{background:rgba(15,23,42,.18);border-radius:99px;}', '.asst-rich .katex{max-width:100%;}', '.asst-md-h{font-weight:800;color:#0F172A;margin:6px 0 2px;}', '.asst-chat{max-height:46vh;overflow:auto;display:flex;flex-direction:column;gap:8px;margin-bottom:8px;}', '.asst-chat:empty{display:none;}', '.asst-msg{font-size:.84rem;line-height:1.5;border-radius:12px;padding:8px 11px;max-width:92%;word-break:break-word;}', '.asst-msg-user{align-self:flex-end;background:#9B5DE5;color:#fff;}', '.asst-msg-assistant{align-self:flex-start;background:rgba(15,23,42,.05);max-width:100%;}', '.asst-msg-assistant .asst-rich{color:#28324a;}', '.asst-msg-ph{opacity:.6;}', '.asst-msg-links{align-self:flex-start;font-size:.74rem;}', '.asst-modes{display:flex;gap:6px;margin:2px 0 8px;}', '.asst-mode{flex:1;border:1px solid #e2e8f0;background:#f8fafc;border-radius:8px;padding:5px 6px;font:700 .68rem Manrope,sans-serif;color:#475569;cursor:pointer;}', '.asst-mode.on{background:#9B5DE5;border-color:#9B5DE5;color:#fff;}', '.asst-src{align-self:flex-start;font-size:.72rem;color:#8a94a6;}', '.asst-src a{color:#7e3eca;font-weight:700;text-decoration:none;}', '.asst-fb{align-self:flex-start;display:flex;gap:6px;}', '.asst-fb button{border:1px solid #e2e8f0;background:#fff;border-radius:7px;width:30px;height:24px;cursor:pointer;color:#8a94a6;display:inline-flex;align-items:center;justify-content:center;}', '.asst-fb button:hover{border-color:#9B5DE5;color:#9B5DE5;}', '.asst-fb button.on{border-color:#9B5DE5;color:#9B5DE5;background:rgba(155,93,229,.1);}', '.asst-fb svg{width:13px;height:13px;}', 'html.asst-exam-on .tc-asst-btn{display:inline-flex !important;}', '.asst-empty{font-size:.82rem;color:#8a94a6;padding:6px 0;}', // на мобиле сайдбар — выезжающая шторка, контент во всю ширину → к левому краю '@media(max-width:768px){.asst-root,.app-layout ~ .asst-root,.app-layout.sb-collapsed ~ .asst-root{left:12px;bottom:18px;}.asst-fab{width:48px;height:48px;}}', ].join(''); document.head.appendChild(s); } /* ── рендер ──────────────────────────────────────────────────────────── */ function setFace(mood) { var f = root.querySelector('.asst-face'); if (f) f.innerHTML = faceSVG(mood); } function openBubble(html, opts) { opts = opts || {}; bubble.innerHTML = '' + html; bubble.querySelector('.asst-x').onclick = closeBubble; bubble.classList.add('open'); openState = true; root.querySelector('.asst-fab').classList.remove('pulse'); root.querySelector('.asst-dot') && root.querySelector('.asst-dot').remove(); if (opts.mood) setFace(opts.mood); } function closeBubble() { bubble.classList.remove('open'); openState = false; setFace(); } function hintHtml(rule) { var act = null; try { act = rule.action(); } catch (e) {} var actHtml = act && act.url ? '' + esc(act.label || 'Открыть') + '' : ''; var dismiss = (rule.scope !== 'celebration') ? '' : ''; return '
$1');
}
function mdToHtml(src) {
var lines = esc(src).split(/\r?\n/), html = '', list = null;
function closeList() { if (list) { html += '' + list + '>'; list = null; } }
for (var i = 0; i < lines.length; i++) {
var ln = lines[i];
var mUl = ln.match(/^\s*[-*]\s+(.*)$/), mOl = ln.match(/^\s*\d+\.\s+(.*)$/), mH = ln.match(/^\s*#{1,6}\s+(.*)$/);
if (mUl) { if (list !== 'ul') { closeList(); html += '