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">
{# Иконка вместо чекбокса #}<div class="me-2 text-center" style="width: 1.25em;">
<i class="bi bi-globe"></i>
</div>
<label class="form-check-label fw-bold me-2">Язык</label>
<select class="form-select form-select-sm w-auto" name="langs"
@change="desc = $event.target.options[$event.target.selectedIndex].dataset.desc"> @change="desc = $event.target.options[$event.target.selectedIndex].dataset.desc">
<option value="ru" selected <option value="ru" selected
data-desc="Только русская типографика: кавычки &laquo;ёлочки&raquo; (&bdquo;вложенные&ldquo;); длинное тире (&mdash;) с&nbsp;пробелами; &laquo;прилипающие&raquo; союзы и&nbsp;предлоги только для&nbsp;русского языка, переносы слов и&nbsp;т.&thinsp;д.">Русский</option> data-desc="Только русская типографика: кавычки &laquo;ёлочки&raquo; (&bdquo;вложенные&ldquo;); длинное тире (&mdash;) с&nbsp;пробелами; &laquo;прилипающие&raquo; союзы и&nbsp;предлоги только для&nbsp;русского языка, переносы слов и&nbsp;т.&thinsp;д.">
Русский
</option>
<option value="en" <option value="en"
data-desc="Только английская типографика: кавычки &ldquo;лапки&rdquo; (&lsquo;вложенные&rsquo;); длинное тире (&mdash;) вплотную; &ldquo;прилипающие&rdquo; союзы и&nbsp;предлоги только для&nbsp;английского языка, переносы слов и&nbsp;т.&thinsp;д.">Английский</option> data-desc="Только английская типографика: кавычки &ldquo;лапки&rdquo; (&lsquo;вложенные&rsquo;); длинное тире (&mdash;) вплотную; &ldquo;прилипающие&rdquo; союзы и&nbsp;предлоги только для&nbsp;английского языка, переносы слов и&nbsp;т.&thinsp;д.">
Английский
</option>
<option value="ru+en" <option value="ru+en"
data-desc="Основной русский <small>(кавычки и&nbsp;тире)</small> + поддержка английского языка для&nbsp;других правил типографики.">Русский + Английский</option> data-desc="Основной русский <small>(кавычки и&nbsp;тире)</small> + поддержка английского языка для&nbsp;других правил типографики.">
<option value="en+ru" data-desc="Основной английский <small>(кавычки и&nbsp;тире)</small> + поддержка русского языка для&nbsp;других правил типографики.">Английский + Русский</option> Русский + Английский
</option>
<option value="en+ru" data-desc="Основной английский <small>(кавычки и&nbsp;тире)</small> + поддержка русского языка для&nbsp;других правил типографики.">Английский +
Русский
</option>
</select> </select>
<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 class="ms-4 ps-1 form-text text-muted small" style="min-height: 2.5em;" x-html="desc"></div>
</div>
<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,15 +109,75 @@
</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>
</div>
{# Настройки переносов #}<div class="ms-3 mt-1" x-show="enabled" x-transition>
<label class="form-label small text-muted mb-0">Макс. длина слова без переноса:</label>
<select class="form-select form-select-sm" name="hyphenation_len">
<option value="8">8 символов</option>
<option value="10">10 символов</option>
<option value="12" selected>12 символов</option>
<option value="14">14 символов</option>
<option value="16">16 символов</option>
</select>
</div>
{# Описание (видно, когда выключено) #}<div class="ms-3 form-text text-muted small" x-show="!enabled" x-transition>
Если отключено, мягкие переносы (&amp;shy;) не будут расставляться.
</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>
{# КОЛОНКА 3 #}<div class="col-md-4">
{# Группа "Висячая пунктуация" #}<h6>Висячая пунктуация</h6>
<select class="form-select form-select-sm mb-2" name="hanging_punctuation">
<option value="none" selected="">Отключена</option>
<option value="left">Только слева</option>
<option value="right">Только справа</option>
<option value="both">С обеих сторон</option>
</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"> @change="desc = $event.target.options[$event.target.selectedIndex].dataset.desc">
<option value="mixed" selected <option value="mixed" selected
data-desc="Оптимально. Спецсимволы (—, ©) как есть, неразрывные пробелы как &nbsp;."> data-desc="Оптимально. Спецсимволы (—, ©) как есть, неразрывные пробелы как &amp;nbsp;.">
Смешанный Смешанный
(Mixed) (Mixed)
</option> </option>
@@ -123,40 +190,9 @@
Мнемоники (&amp;nbsp;) Мнемоники (&amp;nbsp;)
</option> </option>
</select> </select>
<div class="form-text text-muted small mb-2" style="min-height: 2.5em;" x-html="desc"></div>
</div> </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 class="col-md-4">
<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">
<option value="none" selected="">Отключена</option>
<option value="left">Только слева</option>
<option value="right">Только справа</option>
<option value="both">С обеих сторон</option>
</select>
</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'),