mod: настройки типографа (08) оформление

This commit is contained in:
2026-01-03 01:24:36 +03:00
parent 64bc1b1d33
commit 1167f07904
3 changed files with 138 additions and 92 deletions

View File

@@ -3,12 +3,11 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}ETPGRF - Типограф{% endblock %}</title> <title>{% block title %}ETPGRF — единая типографика для веба{% endblock %}</title>
{# Bootstrap 5 CSS #}<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
{# Bootstrap 5 CSS #}<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> {# Bootstrap Icons #}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
{# HTMX #}<script src="https://unpkg.com/htmx.org@1.9.10"></script> {# HTMX #}<script src="https://unpkg.com/htmx.org@1.9.10"></script>
{# Alpine.js #}<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script> {# Alpine.js #}<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style> <style>
/* Небольшие стили для красоты */ /* Небольшие стили для красоты */
body { background-color: #f8f9fa; } body { background-color: #f8f9fa; }

View File

@@ -11,50 +11,60 @@
<textarea class="form-control" name="text" rows="10" placeholder="Вставьте текст сюда..."></textarea> <textarea class="form-control" name="text" rows="10" placeholder="Вставьте текст сюда..."></textarea>
</div> </div>
<!-- Блок настроек (Collapse) --> {# Блок настроек (Collapse) #}<div class="mb-3">
<div class="mb-3">
<button class="btn btn-outline-secondary btn-sm mb-2" type="button" data-bs-toggle="collapse" <button class="btn btn-outline-secondary btn-sm mb-2" type="button" data-bs-toggle="collapse"
data-bs-target="#settingsCollapse" aria-expanded="false" aria-controls="settingsCollapse"> data-bs-target="#settingsCollapse" aria-expanded="false" aria-controls="settingsCollapse">
⚙️ Настройки типографики <i class="bi bi-gear-fill me-1"></i> Настройки типографики
</button> </button>
<div class="collapse" id="settingsCollapse"> <div class="collapse" id="settingsCollapse">
<div class="card card-body bg-light"> <div class="card card-body bg-light">
<div class="row"> <div class="row">
<div class="col-md-4"> {# КОЛОНКА 1 #}<div class="col-md-4">
<h6>Основные</h6> {# Выбор языка (Alpine.js) #}<div class="mb-3"
<!-- Выбор языка (Alpine.js) -->
<div class="mb-3"
x-data="{ desc: 'Только русская типографика: кавычки &laquo;ёлочки&raquo; (&bdquo;вложенные&ldquo;); длинное тире (&mdash;) с&nbsp;пробелами; &laquo;прилипающие&raquo; союзы и&nbsp;предлоги только для&nbsp;русского языка, переносы слов и&nbsp;т.&thinsp;д.' }"> x-data="{ desc: 'Только русская типографика: кавычки &laquo;ёлочки&raquo; (&bdquo;вложенные&ldquo;); длинное тире (&mdash;) с&nbsp;пробелами; &laquo;прилипающие&raquo; союзы и&nbsp;предлоги только для&nbsp;русского языка, переносы слов и&nbsp;т.&thinsp;д.' }">
<label class="form-label small text-muted mb-0">Язык:</label>
<select class="form-select form-select-sm mb-2" name="langs" <div class="d-flex align-items-center mb-1">
@change="desc = $event.target.options[$event.target.selectedIndex].dataset.desc"> {# Иконка вместо чекбокса #}<div class="me-2 text-center" style="width: 1.25em;">
<option value="ru" selected <i class="bi bi-globe"></i>
data-desc="Только русская типографика: кавычки &laquo;ёлочки&raquo; (&bdquo;вложенные&ldquo;); длинное тире (&mdash;) с&nbsp;пробелами; &laquo;прилипающие&raquo; союзы и&nbsp;предлоги только для&nbsp;русского языка, переносы слов и&nbsp;т.&thinsp;д.">Русский</option> </div>
<option value="en" <label class="form-check-label fw-bold me-2">Язык</label>
data-desc="Только английская типографика: кавычки &ldquo;лапки&rdquo; (&lsquo;вложенные&rsquo;); длинное тире (&mdash;) вплотную; &ldquo;прилипающие&rdquo; союзы и&nbsp;предлоги только для&nbsp;английского языка, переносы слов и&nbsp;т.&thinsp;д.">Английский</option> <select class="form-select form-select-sm w-auto" name="langs"
<option value="ru+en" @change="desc = $event.target.options[$event.target.selectedIndex].dataset.desc">
data-desc="Основной русский <small>(кавычки и&nbsp;тире)</small> + поддержка английского языка для&nbsp;других правил типографики.">Русский + Английский</option> <option value="ru" selected
<option value="en+ru" data-desc="Основной английский <small>(кавычки и&nbsp;тире)</small> + поддержка русского языка для&nbsp;других правил типографики.">Английский + Русский</option> data-desc="Только русская типографика: кавычки &laquo;ёлочки&raquo; (&bdquo;вложенные&ldquo;); длинное тире (&mdash;) с&nbsp;пробелами; &laquo;прилипающие&raquo; союзы и&nbsp;предлоги только для&nbsp;русского языка, переносы слов и&nbsp;т.&thinsp;д.">
</select> Русский
<div class="form-text text-muted small mb-2" style="min-height: 2.5em;" x-html="desc"></div> </option>
<option value="en"
data-desc="Только английская типографика: кавычки &ldquo;лапки&rdquo; (&lsquo;вложенные&rsquo;); длинное тире (&mdash;) вплотную; &ldquo;прилипающие&rdquo; союзы и&nbsp;предлоги только для&nbsp;английского языка, переносы слов и&nbsp;т.&thinsp;д.">
Английский
</option>
<option value="ru+en"
data-desc="Основной русский <small>(кавычки и&nbsp;тире)</small> + поддержка английского языка для&nbsp;других правил типографики.">
Русский + Английский
</option>
<option value="en+ru" data-desc="Основной английский <small>(кавычки и&nbsp;тире)</small> + поддержка русского языка для&nbsp;других правил типографики.">Английский +
Русский
</option>
</select>
</div>
{# Описание с отступом, чтобы было под текстом #}<div class="ms-4 ps-1 form-text text-muted small" style="min-height: 2.5em;" x-html="desc"></div>
</div> </div>
<hr class="my-2"> <hr class="my-2" />
{# Группа "Кавычки" #}<div x-data="{ enabled: true }" class="mb-2"> {# Группа "Кавычки" #}<div x-data="{ enabled: true }" class="mb-2">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" name="quotes" id="optQuotes" checked x-model="enabled"> <input class="form-check-input" type="checkbox" name="quotes" id="optQuotes" checked x-model="enabled">
<label class="form-check-label fw-bold" for="optQuotes">Обработка кавычек</label> <label class="form-check-label fw-bold" for="optQuotes">Обработка кавычек</label>
</div> </div>
{# Описание группы "Кавычки" (видно, когда выключено) #} {# Описание (видно, когда выключено) #}<div class="ms-3 form-text text-muted small" x-show="!enabled" x-transition>
<div class="ms-3 form-text text-muted small" x-show="!enabled" x-transition>
Прямые кавычки (&quot;) не будут заменяться на типографские («…ёлочки…» или “…лапки…”). Прямые кавычки (&quot;) не будут заменяться на типографские («…ёлочки…» или “…лапки…”).
</div> </div>
</div> </div>
<hr class="my-2"> <hr class="my-2" />
{# Группа "Компоновка и отбивка" (Layout) #}<div x-data="{ enabled: true }" class="mb-2"> {# Группа "Компоновка и отбивка" (Layout) #}<div x-data="{ enabled: true }" class="mb-2">
<div class="form-check"> <div class="form-check">
@@ -62,21 +72,18 @@
<label class="form-check-label fw-bold" for="optLayout">Компоновка и&nbsp;отбивка</label> <label class="form-check-label fw-bold" for="optLayout">Компоновка и&nbsp;отбивка</label>
</div> </div>
<!-- Настройки (видны, когда включено) --> {# Настройки (видны, когда включено) #}<div class="ms-3" x-show="enabled" x-transition>
<div class="ms-3" x-show="enabled" x-transition>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" name="layout_initials" id="optLayoutInitials" checked> <input class="form-check-input" type="checkbox" name="layout_initials" id="optLayoutInitials" checked>
<label class="form-check-label" for="optLayoutInitials">Инициалы <small class="text-muted">(А.&thinsp;С.&thinsp;Пушкин)</small></label> <label class="form-check-label" for="optLayoutInitials">Инициалы <small class="text-muted">(А.&thinsp;С.&thinsp;Пушкин)</small></label>
</div> </div>
<!-- Единицы измерения с кастомным полем --> {# Единицы измерения с кастомным полем #}<div x-data="{ unitsEnabled: true }" class="form-check">
<div x-data="{ unitsEnabled: true }" class="form-check">
<input class="form-check-input" type="checkbox" name="layout_units" id="optLayoutUnits" checked <input class="form-check-input" type="checkbox" name="layout_units" id="optLayoutUnits" checked
x-model="unitsEnabled"> x-model="unitsEnabled">
<label class="form-check-label" for="optLayoutUnits">Единицы измерения <small class="text-muted">(10&thinsp;км)</small></label> <label class="form-check-label" for="optLayoutUnits">Единицы измерения <small class="text-muted">(10&thinsp;км)</small></label>
<!-- Поле для кастомных единиц --> {# Поле для кастомных единиц #}<div class="mt-1" x-show="unitsEnabled" x-transition>
<div class="mt-1" x-show="unitsEnabled" x-transition>
<input type="text" class="form-control form-control-sm" name="layout_units_custom" <input type="text" class="form-control form-control-sm" name="layout_units_custom"
placeholder="Доп. единицы (через пробел): бит байт"> placeholder="Доп. единицы (через пробел): бит байт">
</div> </div>
@@ -88,9 +95,9 @@
не&nbsp;будет произведена. не&nbsp;будет произведена.
</div> </div>
</div> </div>
</div>
<hr class="my-2"> {# КОЛОНКА 2 #}<div class="col-md-4">
{# Группа "Неразрывные пробелы" #}<div x-data="{ enabled: true }" class="mb-2"> {# Группа "Неразрывные пробелы" #}<div x-data="{ enabled: true }" class="mb-2">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" name="unbreakables" id="optUnbreakables" checked x-model="enabled"> <input class="form-check-input" type="checkbox" name="unbreakables" id="optUnbreakables" checked x-model="enabled">
@@ -102,61 +109,90 @@
</div> </div>
</div> </div>
<hr class="my-2"> <hr class="my-2" />
<!-- Режим вывода (Alpine.js) --> {# Группа "Переносы" #}<div x-data="{ enabled: true }" class="mb-2">
<div x-data="{ desc: 'Оптимально. Спецсимволы (—, ©) как есть, неразрывные пробелы как &nbsp;.' }"> <div class="form-check">
<label class="form-label small text-muted mb-0">Режим вывода:</label> <input class="form-check-input" type="checkbox" name="hyphenation" id="optHyphenation" checked x-model="enabled">
<select class="form-select form-select-sm" name="mode" <label class="form-check-label fw-bold" for="optHyphenation">Переносы внутри длинных слов&nbsp;(&amp;shy;)</label>
@change="desc = $event.target.options[$event.target.selectedIndex].dataset.desc"> </div>
<option value="mixed" selected
data-desc="Оптимально. Спецсимволы (—, ©) как есть, неразрывные пробелы как &nbsp;."> {# Настройки переносов #}<div class="ms-3 mt-1" x-show="enabled" x-transition>
Смешанный <label class="form-label small text-muted mb-0">Макс. длина слова без переноса:</label>
(Mixed) <select class="form-select form-select-sm" name="hyphenation_len">
</option> <option value="8">8 символов</option>
<option value="unicode" <option value="10">10 символов</option>
data-desc="Компактно. Все символы в UTF-8 (включая неразрывный пробел U+00A0)."> <option value="12" selected>12 символов</option>
Юникод (Unicode) <option value="14">14 символов</option>
</option> <option value="16">16 символов</option>
<option value="mnemonic" </select>
data-desc="Совместимость. Все спецсимволы заменяются на HTML-сущности (&amp;mdash;, &amp;copy;)."> </div>
Мнемоники (&amp;nbsp;)
</option> {# Описание (видно, когда выключено) #}<div class="ms-3 form-text text-muted small" x-show="!enabled" x-transition>
</select> Если отключено, мягкие переносы (&amp;shy;) не будут расставляться.
<div class="form-text text-muted small mb-2" style="min-height: 2.5em;" x-html="desc"></div> </div>
</div>
<hr class="my-2" />
{# Группа "Символы" #}<div x-data="{ enabled: true }" class="mb-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="symbols" id="optSymbols" checked x-model="enabled">
<label class="form-check-label fw-bold" for="optSymbols">Символы ((c) &rarr; ©)</label>
</div>
{# Описание (видно, когда выключено) #}<div class="ms-3 form-text text-muted small" x-show="!enabled" x-transition>
Замена псевдографики (стрелочки, копирайты) на&nbsp;спецсимволы отключена.
</div>
</div> </div>
</div> </div>
<div class="col-md-4"> {# КОЛОНКА 3 #}<div class="col-md-4">
<h6>Дополнительно</h6> {# Группа "Висячая пунктуация" #}<h6>Висячая пунктуация</h6>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="hyphenation" id="optHyphenation" checked>
<label class="form-check-label" for="optHyphenation">Переносы слов (&shy;)</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" name="symbols" id="optSymbols" checked>
<label class="form-check-label" for="optSymbols">Символы ((c) &rarr; ©)</label>
</div>
<!-- Санитайзер -->
<div class="mt-3">
<label class="form-label small text-muted mb-0">Очистка (Sanitizer):</label>
<select class="form-select form-select-sm" name="sanitizer">
<option value="" selected>Без очистки</option>
<option value="etp">Удалить старую типографику</option>
<option value="html">Удалить все HTML-теги</option>
</select>
</div>
</div>
<div class="col-md-4">
<h6>Висячая пунктуация</h6>
<select class="form-select form-select-sm mb-2" name="hanging_punctuation"> <select class="form-select form-select-sm mb-2" name="hanging_punctuation">
<option value="none" selected="">Отключена</option> <option value="none" selected="">Отключена</option>
<option value="left">Только слева</option> <option value="left">Только слева</option>
<option value="right">Только справа</option> <option value="right">Только справа</option>
<option value="both">С обеих сторон</option> <option value="both">С обеих сторон</option>
</select> </select>
<hr class="my-2" />
{# Санитайзер #}<div class="mt-3 mb-3">
<label class="form-label fw-bold mb-0">Очистка (Sanitizer):</label>
<select class="form-select form-select-sm" name="sanitizer">
<option value="" selected>Без очистки</option>
<option value="etp">Удалить старую типографику</option>
<option value="html">Удалить все HTML-теги</option>
</select>
</div>
<hr class="my-2" />
{# Режим вывода (Alpine.js) #}<div x-data="{ desc: 'Оптимально. Спецсимволы (—, ©) как есть, неразрывные пробелы как &nbsp;.' }">
<div class="d-flex align-items-center mb-1">
<div class="me-2 text-center" style="width: 1.25em;">
<i class="bi bi-code-slash"></i>
</div>
<label class="form-check-label fw-bold me-2">Режим вывода</label>
<select class="form-select form-select-sm w-auto" name="mode"
@change="desc = $event.target.options[$event.target.selectedIndex].dataset.desc">
<option value="mixed" selected
data-desc="Оптимально. Спецсимволы (—, ©) как есть, неразрывные пробелы как &amp;nbsp;.">
Смешанный
(Mixed)
</option>
<option value="unicode"
data-desc="Компактно. Все символы в UTF-8 (включая неразрывный пробел U+00A0).">
Юникод (Unicode)
</option>
<option value="mnemonic"
data-desc="Совместимость. Все спецсимволы заменяются на HTML-сущности (&amp;mdash;, &amp;copy;).">
Мнемоники (&amp;nbsp;)
</option>
</select>
</div>
<div class="ms-4 ps-1 form-text text-muted small mb-2" style="min-height: 2.5em;" x-html="desc"></div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -2,6 +2,7 @@ from django.shortcuts import render
from django.http import HttpResponse from django.http import HttpResponse
from etpgrf.typograph import Typographer from etpgrf.typograph import Typographer
from etpgrf.layout import LayoutProcessor from etpgrf.layout import LayoutProcessor
from etpgrf.hyphenation import Hyphenator
def index(request): def index(request):
return render(request, template_name='typograph/index.html') return render(request, template_name='typograph/index.html')
@@ -15,40 +16,50 @@ def process_text(request):
# 2. Собираем LayoutProcessor # 2. Собираем LayoutProcessor
layout_enabled = request.POST.get('layout') == 'on' layout_enabled = request.POST.get('layout') == 'on'
layout_option = False # По умолчанию выключен layout_option = False
if layout_enabled: if layout_enabled:
# Обработка единиц измерения
process_units = request.POST.get('layout_units') == 'on' process_units = request.POST.get('layout_units') == 'on'
if process_units: if process_units:
# Если включено, проверяем кастомные единицы
custom_units = request.POST.get('layout_units_custom', '').strip() custom_units = request.POST.get('layout_units_custom', '').strip()
if custom_units: if custom_units:
# Если есть кастомные, передаем их списком (библиотека сама объединит с дефолтными)
process_units = custom_units.split() process_units = custom_units.split()
# Если включен, создаем процессор с тонкими настройками
layout_option = LayoutProcessor( layout_option = LayoutProcessor(
langs=langs, langs=langs,
process_initials_and_acronyms=request.POST.get('layout_initials') == 'on', process_initials_and_acronyms=request.POST.get('layout_initials') == 'on',
process_units=process_units process_units=process_units
) )
# 3. Читаем Sanitizer # 3. Собираем Hyphenator
hyphenation_enabled = request.POST.get('hyphenation') == 'on'
hyphenation_option = False
if hyphenation_enabled:
max_len = request.POST.get('hyphenation_len', '15')
try:
max_len = int(max_len)
except (ValueError, TypeError):
max_len = 15 # Дефолтное значение, если пришло что-то не то
hyphenation_option = Hyphenator(
langs=langs,
max_unhyphenated_len=max_len
)
# 4. Читаем Sanitizer
sanitizer_val = request.POST.get('sanitizer', '') sanitizer_val = request.POST.get('sanitizer', '')
sanitizer_option = None sanitizer_option = None
if sanitizer_val: if sanitizer_val:
sanitizer_option = sanitizer_val # 'etp' или 'html' sanitizer_option = sanitizer_val
# 4. Собираем общие опции # 5. Собираем общие опции
options = { options = {
'langs': langs, 'langs': langs,
'process_html': True, 'process_html': True,
'quotes': request.POST.get('quotes') == 'on', 'quotes': request.POST.get('quotes') == 'on',
'layout': layout_option, # Передаем объект или False 'layout': layout_option,
'unbreakables': request.POST.get('unbreakables') == 'on', 'unbreakables': request.POST.get('unbreakables') == 'on',
'hyphenation': request.POST.get('hyphenation') == 'on', 'hyphenation': hyphenation_option,
'symbols': request.POST.get('symbols') == 'on', 'symbols': request.POST.get('symbols') == 'on',
'hanging_punctuation': request.POST.get(key='hanging_punctuation', default='none'), 'hanging_punctuation': request.POST.get(key='hanging_punctuation', default='none'),