188 lines
6.6 KiB
Bash
Executable File
188 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# Скрипт создаёт временную рабочую папку, ставит зависимости через `npm ci`, собирает
|
||
# минимизированный бандл и затем сам удаляет временные `src/` и `node_modules/`.
|
||
# В проекте остаётся только готовая статика:
|
||
# * `public/static/codemirror/editor.js`
|
||
#
|
||
# Запуск:
|
||
# bash ./frontend-assembly/build-codemirror6.sh
|
||
|
||
set -euo pipefail
|
||
|
||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
OUTPUT_DIR="$ROOT_DIR/../public/static/codemirror"
|
||
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/codemirror6.XXXXXX")"
|
||
|
||
log() {
|
||
printf '[codemirror6] %s\n' "$*"
|
||
}
|
||
|
||
fail() {
|
||
printf '[codemirror6] %s\n' "$*" >&2
|
||
exit 1
|
||
}
|
||
|
||
cleanup() {
|
||
rm -rf "$WORK_DIR"
|
||
rm -rf "$ROOT_DIR/src" "$ROOT_DIR/node_modules"
|
||
}
|
||
|
||
trap cleanup EXIT INT TERM
|
||
|
||
if ! command -v npm >/dev/null 2>&1; then
|
||
fail 'Не найден `npm`. Установи Node.js и повтори сборку.'
|
||
fi
|
||
|
||
if [[ ! -f "$ROOT_DIR/package.json" ]]; then
|
||
fail "Не найден package.json: $ROOT_DIR/package.json"
|
||
fi
|
||
|
||
if [[ ! -f "$ROOT_DIR/package-lock.json" ]]; then
|
||
fail "Не найден package-lock.json: $ROOT_DIR/package-lock.json"
|
||
fi
|
||
|
||
mkdir -p "$WORK_DIR/src" "$OUTPUT_DIR"
|
||
cp "$ROOT_DIR/package.json" "$ROOT_DIR/package-lock.json" "$WORK_DIR/"
|
||
|
||
cat > "$WORK_DIR/src/editor.js" <<'EOF'
|
||
import { Compartment, EditorState } from '@codemirror/state';
|
||
import { EditorView } from '@codemirror/view';
|
||
import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||
import { html } from '@codemirror/lang-html';
|
||
import { javascript } from '@codemirror/lang-javascript';
|
||
import { json } from '@codemirror/lang-json';
|
||
import { css } from '@codemirror/lang-css';
|
||
import { solarizedDark, solarizedLight } from '@uiw/codemirror-theme-solarized';
|
||
import { lineNumbers } from '@codemirror/view';
|
||
|
||
const themeCompartment = new Compartment();
|
||
const processedForms = new Set(); // Храним формы, на которые уже повесили обработчик
|
||
|
||
function isDarkTheme() {
|
||
const rootTheme = document.documentElement.dataset.theme;
|
||
if (rootTheme === 'dark') return true;
|
||
if (rootTheme === 'light') return false;
|
||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||
}
|
||
|
||
function reconfigureTheme(view) {
|
||
view.dispatch({
|
||
effects: themeCompartment.reconfigure(isDarkTheme() ? solarizedDark : solarizedLight),
|
||
});
|
||
}
|
||
|
||
function initCodeMirrorEditors() {
|
||
document.querySelectorAll('textarea[data-codemirror-editor]').forEach((textarea) => {
|
||
const language = textarea.dataset.language || 'text';
|
||
let initialDoc = textarea.value ?? '';
|
||
const wrapper = document.createElement('div');
|
||
wrapper.className = 'cm6-editor-wrapper';
|
||
textarea.insertAdjacentElement('beforebegin', wrapper);
|
||
|
||
// --- Beautify JSON on load ---
|
||
if (language === 'json') {
|
||
try {
|
||
const parsed = JSON.parse(initialDoc);
|
||
initialDoc = JSON.stringify(parsed, null, 2); // Форматируем с отступом в 2 пробела
|
||
} catch (e) {
|
||
// Если в поле невалидный JSON, оставляем как есть
|
||
console.warn("CodeMirror: Initial content is not valid JSON, displaying as is.", e);
|
||
}
|
||
}
|
||
|
||
const syncTextarea = EditorView.updateListener.of((update) => {
|
||
if (update.docChanged) {
|
||
// Синхронизируем "красивый" JSON в textarea для немедленного отображения
|
||
textarea.value = update.state.doc.toString();
|
||
}
|
||
});
|
||
|
||
const extensions = [
|
||
EditorView.lineWrapping,
|
||
syntaxHighlighting(defaultHighlightStyle),
|
||
syncTextarea,
|
||
themeCompartment.of(isDarkTheme() ? solarizedDark : solarizedLight),
|
||
];
|
||
|
||
// Добавляем нумерацию строк, если не указано обратное
|
||
if (!textarea.classList.contains('codemirror-no-lines')) {
|
||
extensions.unshift(lineNumbers());
|
||
}
|
||
|
||
if (language === 'javascript') {
|
||
extensions.unshift(javascript());
|
||
} else if (language === 'css') {
|
||
extensions.unshift(css());
|
||
} else if (language === 'json') {
|
||
extensions.unshift(json());
|
||
} else if (language === 'html') {
|
||
extensions.unshift(html());
|
||
}
|
||
// Для 'text' язык не добавляется, будет обычное поле
|
||
|
||
const state = EditorState.create({
|
||
doc: initialDoc,
|
||
extensions,
|
||
});
|
||
|
||
const view = new EditorView({
|
||
state,
|
||
parent: wrapper,
|
||
});
|
||
|
||
// Сохраняем ссылку на инстанс редактора для последующего доступа
|
||
textarea.cmView = view;
|
||
|
||
reconfigureTheme(view);
|
||
|
||
const observer = new MutationObserver(() => reconfigureTheme(view));
|
||
observer.observe(document.documentElement, {
|
||
attributes: true,
|
||
attributeFilter: ['data-theme', 'class'],
|
||
});
|
||
|
||
const colorScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||
colorScheme.addEventListener('change', () => reconfigureTheme(view));
|
||
|
||
// --- Minify JSON on save ---
|
||
const form = textarea.closest('form');
|
||
if (form && !processedForms.has(form)) {
|
||
form.addEventListener('submit', () => {
|
||
form.querySelectorAll('textarea[data-language="json"]').forEach(jsonTextarea => {
|
||
if (jsonTextarea.cmView) {
|
||
const prettyJson = jsonTextarea.cmView.state.doc.toString();
|
||
try {
|
||
const parsed = JSON.parse(prettyJson);
|
||
const minifiedJson = JSON.stringify(parsed);
|
||
jsonTextarea.value = minifiedJson; // Подменяем значение на сжатое
|
||
} catch (e) {
|
||
// Если пользователь ввел невалидный JSON, позволяем Django его отвергнуть
|
||
console.warn("CodeMirror: Could not minify invalid JSON before submit. Django will likely reject this.", e);
|
||
}
|
||
}
|
||
});
|
||
});
|
||
processedForms.add(form);
|
||
}
|
||
});
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', initCodeMirrorEditors, { once: true });
|
||
} else {
|
||
initCodeMirrorEditors();
|
||
}
|
||
EOF
|
||
|
||
log "СОБИРАЮ CodeMirror 6 ДЛЯ ФРОНТЕНДА АДМИНКИ ПРОЕКТА"
|
||
log "Временная рабочая папка: $WORK_DIR"
|
||
cd "$WORK_DIR"
|
||
|
||
log 'Устанавливаю зависимости через npm ci'
|
||
npm ci
|
||
|
||
log 'Собираю CodeMirror 6'
|
||
export CM6_OUTPUT_DIR="$OUTPUT_DIR"
|
||
npm run build
|
||
|
||
log 'ГОТОВО' |