Files
LMS/web/src/main/resources/static/js/mdeditor.js
Vladislav Kopnin 556fdf3b5e Redesign B3 bordeaux + finish lab-page rework
- Lab page is now read-only with a settings screen for levels/variants;
  variant issuance moved to a large modal on the group page (assign-fragment).
- Header: bordeaux logo and 2px accent underline, bordeaux login/logout/theme
  buttons; smaller radii across the UI; filled secondary/danger buttons.
- Includes prior agent work (login backoff, no-cache pages, API timeouts,
  content tweaks) and fixes (variant usage Map.values, import order).
- Bump static asset version to v=9.
2026-10-02 15:41:16 +03:00

137 lines
5.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Редактор markdown: панель кнопок применяется к textarea по data-md-target.
Плюс переключение статьи в режим правки прямо на странице (без модального окна). */
(function () {
var dirty = false;
function target(toolbar) {
return document.querySelector(toolbar.getAttribute('data-md-target'));
}
function replaceSelection(area, before, after, placeholder) {
var start = area.selectionStart, end = area.selectionEnd;
var selected = area.value.slice(start, end) || placeholder || '';
var text = before + selected + after;
area.setRangeText(text, start, end, 'end');
if (!area.selectionEnd || selected === placeholder) {
// оставляем курсор внутри вставленного фрагмента
area.selectionStart = start + before.length;
area.selectionEnd = start + before.length + selected.length;
}
area.focus();
area.dispatchEvent(new Event('input', { bubbles: true }));
}
function prefixLines(area, prefix, numbered) {
var start = area.selectionStart, end = area.selectionEnd;
var value = area.value;
var lineStart = value.lastIndexOf('\n', start - 1) + 1;
var lineEnd = value.indexOf('\n', end);
if (lineEnd === -1) lineEnd = value.length;
var block = value.slice(lineStart, lineEnd);
var lines = block.split('\n');
var changed = lines.map(function (line, index) {
if (line.trim() === '') return line;
return (numbered ? (index + 1) + '. ' : prefix) + line;
}).join('\n');
area.setRangeText(changed, lineStart, lineEnd, 'end');
area.focus();
area.dispatchEvent(new Event('input', { bubbles: true }));
}
function insertBlock(area, text, cursorOffset) {
var start = area.selectionStart;
var value = area.value;
var atLineStart = start === 0 || value[start - 1] === '\n';
var prefix = atLineStart ? '' : '\n';
var insert = prefix + text + '\n';
area.setRangeText(insert, start, area.selectionEnd, 'end');
var pos = area.selectionStart - 1 - (text.length - (cursorOffset || 0));
area.selectionStart = area.selectionEnd = start + prefix.length + (cursorOffset || 0);
area.focus();
area.dispatchEvent(new Event('input', { bubbles: true }));
}
function apply(action, area) {
switch (action) {
case 'bold': replaceSelection(area, '**', '**', 'текст'); break;
case 'italic': replaceSelection(area, '*', '*', 'текст'); break;
case 'code': replaceSelection(area, '`', '`', 'код'); break;
case 'h2': prefixLines(area, '## '); break;
case 'ul': prefixLines(area, '- '); break;
case 'ol': prefixLines(area, '', true); break;
case 'quote': prefixLines(area, '> '); break;
case 'link': replaceSelection(area, '[', '](https://)', 'ссылка'); break;
case 'codeblock':
replaceSelection(area, '```c\n', '\n```', 'int main(void)\n{\n return 0;\n}');
break;
case 'table':
insertBlock(area, '| Столбец | Столбец |\n|---|---|\n| значение | значение |', 2);
break;
case 'figure':
replaceSelection(area, ':::figure assets/', ' | Рис. 1. Подпись', 'имя.svg');
break;
case 'video':
replaceSelection(area, ':::video assets/', ' | Подпись к видео', 'имя.mp4');
break;
case 'spoiler':
replaceSelection(area, ':::spoiler Решение\n', '\n:::', 'решение');
break;
}
}
document.addEventListener('click', function (event) {
var button = event.target.closest('.md-btn');
if (!button) return;
var toolbar = button.closest('.md-toolbar');
if (!toolbar) return;
var area = target(toolbar);
if (!area) return;
event.preventDefault();
apply(button.getAttribute('data-md'), area);
});
/* --- режим правки статьи на странице --- */
window.startArticleEdit = function () {
var view = document.getElementById('article-view');
var form = document.getElementById('article-edit');
if (!view || !form) return;
view.hidden = true;
form.hidden = false;
dirty = false;
var area = document.getElementById('article-body');
if (area) area.focus();
};
window.cancelArticleEdit = function () {
if (dirty && !confirm('Выйти из редактирования без сохранения?')) return;
var view = document.getElementById('article-view');
var form = document.getElementById('article-edit');
if (!view || !form) return;
form.hidden = true;
view.hidden = false;
dirty = false;
};
document.addEventListener('input', function (event) {
var id = event.target && event.target.id;
if (id === 'article-body' || id === 'article-title') dirty = true;
});
/* Сохранение — это осознанный уход со страницы, подтверждение не нужно */
document.addEventListener('submit', function (event) {
if (event.target && event.target.id === 'article-edit') dirty = false;
}, true);
window.addEventListener('beforeunload', function (event) {
var form = document.getElementById('article-edit');
if (dirty && form && !form.hidden) {
event.preventDefault();
event.returnValue = '';
}
});
document.addEventListener('DOMContentLoaded', function () {
if (window.location.search.indexOf('edit=1') >= 0) window.startArticleEdit();
});
})();