feat: htmx setup
This commit is contained in:
@@ -29,6 +29,7 @@ THIRD_PARTY_APPS = [
|
||||
'corsheaders',
|
||||
'django_extensions',
|
||||
'django_filters',
|
||||
'django_htmx',
|
||||
'watchman',
|
||||
]
|
||||
|
||||
@@ -55,6 +56,7 @@ MIDDLEWARE = [
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django_htmx.middleware.HtmxMiddleware',
|
||||
]
|
||||
|
||||
if DEBUG:
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ lang }}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Crochet{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{% static 'css/main.css' %}">
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
|
||||
<body{% block body_attrs %}{% endblock %}>
|
||||
{% block content %}{% endblock %}
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,33 +1,28 @@
|
||||
{% load static i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ lang }}">
|
||||
{% extends 'crochet/base.html' %}
|
||||
{% load static i18n django_htmx %}
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% block title %}Crochet{% endblock %}
|
||||
|
||||
{% block body_attrs %} hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<!-- Leído por storage.js para la cabecera X-CSRFToken al guardar por fetch. -->
|
||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
||||
<title>Crochet</title>
|
||||
<link rel="stylesheet" href="{% static 'css/main.css' %}">
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<!-- Rellenada por js/page-settings.js con el @page (tamaño/orientación)
|
||||
elegido en "Personalización de página": un <style> aparte porque
|
||||
@page no se puede cambiar como una propiedad normal desde JS. -->
|
||||
<style id="page-size-style"></style>
|
||||
</head>
|
||||
{% htmx_script %}
|
||||
|
||||
<body>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3/dist/cdn.min.js"></script>
|
||||
<style id="page-size-style"></style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main>
|
||||
|
||||
<header class="flex flex-wrap items-center justify-between gap-2 p-4 no-print">
|
||||
<h1 class="text-xl font-semibold">Crochet</h1>
|
||||
<div class="flex gap-2 items-center">
|
||||
<!-- Idioma del contenido (título/subtítulo/texto de cada sección), no
|
||||
de la interfaz: la estructura de secciones es la misma para
|
||||
todos los idiomas, solo cambian los textos que el usuario ha
|
||||
escrito para el idioma seleccionado. Añadir un idioma nuevo es
|
||||
solo cuestión de añadir una <option> aquí. -->
|
||||
|
||||
<select id="language-select" class="select select-sm select-bordered">
|
||||
<option value="es" {% if lang == 'es' %}selected{% endif %}>Español</option>
|
||||
<option value="en" {% if lang == 'en' %}selected{% endif %}>English</option>
|
||||
@@ -35,18 +30,13 @@
|
||||
<button id="save-pattern" type="button" class="btn btn-sm btn-outline"
|
||||
data-save-url="{% url 'crochet:pattern_save' pattern.uuid %}"
|
||||
data-error-message="{% trans 'No se ha podido guardar. Inténtalo de nuevo.' %}">{% trans 'Guardar' %}</button>
|
||||
<!-- Enlace normal, no un botón con JS: solo navega a la vista de
|
||||
solo lectura (ver PatternDetailView), que se guarda tal cual
|
||||
desde la última vez que se pulsó "Guardar". target="_blank"
|
||||
para no perder el editor con sus cambios sin guardar. -->
|
||||
|
||||
<a href="{% url 'crochet:pattern_detail' pattern.uuid %}" target="_blank" rel="noopener"
|
||||
class="btn btn-sm btn-outline">{% trans 'Ver patrón' %}</a>
|
||||
<button id="export-pdf" type="button" class="btn btn-sm">{% trans 'Exportar a PDF' %}</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Aviso breve de que "Guardar" ha funcionado: sin esto, guardar en
|
||||
localStorage no da ninguna señal de que ha ocurrido algo. -->
|
||||
<div id="save-toast" class="toast toast-top toast-end no-print hidden">
|
||||
<div class="alert alert-success">
|
||||
<span data-message="{% trans 'Guardado' %}">{% trans 'Guardado' %}</span>
|
||||
@@ -55,10 +45,6 @@
|
||||
|
||||
<div class="p-4 pt-0">
|
||||
|
||||
<!-- Configuración única para todo el documento (no es una sección más:
|
||||
no se repite, no se reordena ni se duplica), por eso vive fuera
|
||||
de #panel-top-sections. <details> nativo para no necesitar JS
|
||||
solo para plegarla/desplegarla. -->
|
||||
<details class="border border-base-300 rounded-box p-4 mb-4 no-print">
|
||||
<summary class="text-lg font-semibold cursor-pointer select-none">{% trans 'Personalización de página' %}</summary>
|
||||
<div class="flex flex-col gap-4 mt-4">
|
||||
@@ -145,13 +131,7 @@
|
||||
<button id="toggle-collapse-all" type="button" class="btn btn-xs btn-outline"
|
||||
data-collapse-all-label="{% trans 'Colapsar todo' %}" data-expand-all-label="{% trans 'Expandir todo' %}">{% trans 'Colapsar todo' %}</button>
|
||||
</div>
|
||||
<!-- Un único botón con menú (en vez de 7-8 botones sueltos que se
|
||||
partían en varias filas en pantallas estrechas): menos ruido
|
||||
visual antes de llegar al contenido real. daisyUI abre
|
||||
.dropdown-content con :focus-within, sin JS propio; el mismo
|
||||
botón (clonado) se reutiliza dentro de cada "Grupo" (ver
|
||||
addGroupSection en section-types.js), así que su
|
||||
data-label también viaja con él. -->
|
||||
|
||||
<div class="dropdown mb-4">
|
||||
<button id="add-section-toggle" type="button" class="btn btn-sm" tabindex="0"
|
||||
data-label="{% trans 'Añadir sección' %}">+ {% trans 'Añadir sección' %}</button>
|
||||
@@ -195,17 +175,12 @@
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Catálogo de tipos de punto arrastrables (StitchType), leído por
|
||||
dnd-items.js. Antes era una lista fija en este HTML; ahora viene de
|
||||
la base de datos, con sus propias traducciones por idioma. -->
|
||||
{{ stitch_types|json_script:"stitch-types" }}
|
||||
|
||||
<!-- Contenido guardado de este patrón (sections/pageSettings), leído
|
||||
por main.js al arrancar. json_script escapa el JSON de forma
|
||||
segura, así que nunca hay que interpolarlo a mano en un <script>. -->
|
||||
{{ pattern_data|json_script:"pattern-data" }}
|
||||
</main>
|
||||
</body>
|
||||
<script type="module" src="{% static 'js/main.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
</html>
|
||||
{% block extra_js %}
|
||||
<script type="module" src="{% static 'js/main.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
{% extends 'crochet/base.html' %}
|
||||
{% load static i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ lang }}">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ page_settings.title|default:"Crochet" }}</title>
|
||||
<link rel="stylesheet" href="{% static 'css/main.css' %}">
|
||||
<!-- CSS de Tailwind precompilado (ver .tools/build/ y css/README-pattern-detail.md),
|
||||
no el CDN con compilador JIT en JS que usa pattern.html: WeasyPrint
|
||||
(ver PatternPdfView) no ejecuta JavaScript, así que ese CDN no
|
||||
generaría ningún estilo al convertir esta misma página a PDF. Ninguna
|
||||
<script> propia tampoco: esta página es puramente de lectura,
|
||||
generada del todo en el servidor (ver pattern_render.py). No hay
|
||||
ningún <input>/<button> de edición que "revelar" quitando una clase
|
||||
desde las herramientas de desarrollo, porque ese marcado no existe
|
||||
aquí (a diferencia de la vista de edición, que sí lo necesita). -->
|
||||
{% block title %}{{ page_settings.title|default:"Crochet" }}{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'css/pattern-detail.css' %}">
|
||||
<style>
|
||||
:root {
|
||||
@@ -24,19 +12,11 @@
|
||||
}
|
||||
@page { size: {{ page_settings.pageSize }} {{ page_settings.orientation }}; }
|
||||
</style>
|
||||
</head>
|
||||
{% endblock %}
|
||||
|
||||
<body>
|
||||
{% block content %}
|
||||
<main class="p-4">
|
||||
<!-- Enlaces normales (sin JS): el tag "language" de i18n activa
|
||||
temporalmente ese idioma para que "url" resuelva la ruta ya
|
||||
traducida (ver crochet/urls.py), igual que hace LocaleMiddleware
|
||||
por petición. no-print para que no aparezca en el PDF exportado
|
||||
(impresión nativa del navegador sobre esta misma página). -->
|
||||
<div class="flex justify-end gap-2 mb-4 no-print">
|
||||
<!-- Genera el PDF en el servidor con WeasyPrint (ver PatternPdfView):
|
||||
renderiza esta misma plantilla (ya con el CSS precompilado, sin
|
||||
depender de ningún JS) directamente a PDF. -->
|
||||
<a href="{% url 'crochet:pattern_pdf' pattern.uuid %}" class="detail-btn">{% trans 'Descargar PDF' %}</a>
|
||||
{% if lang != 'es' %}
|
||||
{% language 'es' %}
|
||||
@@ -57,6 +37,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
@@ -10,6 +10,7 @@ dependencies = [
|
||||
"django-environ>=0.14.0",
|
||||
"django-extensions>=4.1",
|
||||
"django-filter>=26.1",
|
||||
"django-htmx>=1.28.0",
|
||||
"django-silk>=5.5.0",
|
||||
"django-storages>=1.14.6",
|
||||
"ipython>=9.15.0",
|
||||
|
||||
@@ -239,6 +239,7 @@ dependencies = [
|
||||
{ name = "django-environ" },
|
||||
{ name = "django-extensions" },
|
||||
{ name = "django-filter" },
|
||||
{ name = "django-htmx" },
|
||||
{ name = "django-silk" },
|
||||
{ name = "django-storages" },
|
||||
{ name = "ipython" },
|
||||
@@ -268,6 +269,7 @@ requires-dist = [
|
||||
{ name = "django-environ", specifier = ">=0.14.0" },
|
||||
{ name = "django-extensions", specifier = ">=4.1" },
|
||||
{ name = "django-filter", specifier = ">=26.1" },
|
||||
{ name = "django-htmx", specifier = ">=1.28.0" },
|
||||
{ name = "django-silk", specifier = ">=5.5.0" },
|
||||
{ name = "django-storages", specifier = ">=1.14.6" },
|
||||
{ name = "ipython", specifier = ">=9.15.0" },
|
||||
@@ -398,6 +400,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/01/afffed1e3c4540fb75bf550a18b6176a9f6371b5f3e52b69a28995b6480c/django_filter-26.1-py3-none-any.whl", hash = "sha256:7d98ef2899218e6242619b532cb1b95af14e09dfcf74844aecb550ad27b59ff2", size = 94069, upload-time = "2026-07-11T09:27:01.012Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-htmx"
|
||||
version = "1.28.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "asgiref" },
|
||||
{ name = "django" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/d9/b31339fae8ede6a7cb0b9facaefe2ee4fc01d9f37a31a3dc03759765a2c3/django_htmx-1.28.0.tar.gz", hash = "sha256:699e229de0bbf19b3ee1a5d419702f5c154fa956e6921fec89faa7401b99e536", size = 100946, upload-time = "2026-07-12T22:19:19.589Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/d5/71b3a206f7a2135e2ab4a789be143a00222b52d22729c84f480915049e94/django_htmx-1.28.0-py3-none-any.whl", hash = "sha256:e6911acccf3e7905d511c4fd6ab86fe06572205e80a0f32ca7aa94a115ae0619", size = 98588, upload-time = "2026-07-12T22:19:18.225Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "django-silk"
|
||||
version = "5.5.0"
|
||||
|
||||
Reference in New Issue
Block a user