feat: htmx setup
This commit is contained in:
@@ -29,6 +29,7 @@ THIRD_PARTY_APPS = [
|
|||||||
'corsheaders',
|
'corsheaders',
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
|
'django_htmx',
|
||||||
'watchman',
|
'watchman',
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -55,6 +56,7 @@ MIDDLEWARE = [
|
|||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'django_htmx.middleware.HtmxMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
if DEBUG:
|
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 %}
|
{% extends 'crochet/base.html' %}
|
||||||
<!DOCTYPE html>
|
{% load static i18n django_htmx %}
|
||||||
<html lang="{{ lang }}">
|
|
||||||
|
|
||||||
<head>
|
{% block title %}Crochet{% endblock %}
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
{% 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. -->
|
<!-- Leído por storage.js para la cabecera X-CSRFToken al guardar por fetch. -->
|
||||||
<meta name="csrf-token" content="{{ csrf_token }}">
|
<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" />
|
<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>
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||||
<!-- Rellenada por js/page-settings.js con el @page (tamaño/orientación)
|
{% htmx_script %}
|
||||||
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>
|
|
||||||
|
|
||||||
<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>
|
<main>
|
||||||
|
|
||||||
<header class="flex flex-wrap items-center justify-between gap-2 p-4 no-print">
|
<header class="flex flex-wrap items-center justify-between gap-2 p-4 no-print">
|
||||||
<h1 class="text-xl font-semibold">Crochet</h1>
|
<h1 class="text-xl font-semibold">Crochet</h1>
|
||||||
<div class="flex gap-2 items-center">
|
<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">
|
<select id="language-select" class="select select-sm select-bordered">
|
||||||
<option value="es" {% if lang == 'es' %}selected{% endif %}>Español</option>
|
<option value="es" {% if lang == 'es' %}selected{% endif %}>Español</option>
|
||||||
<option value="en" {% if lang == 'en' %}selected{% endif %}>English</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"
|
<button id="save-pattern" type="button" class="btn btn-sm btn-outline"
|
||||||
data-save-url="{% url 'crochet:pattern_save' pattern.uuid %}"
|
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>
|
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"
|
<a href="{% url 'crochet:pattern_detail' pattern.uuid %}" target="_blank" rel="noopener"
|
||||||
class="btn btn-sm btn-outline">{% trans 'Ver patrón' %}</a>
|
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>
|
<button id="export-pdf" type="button" class="btn btn-sm">{% trans 'Exportar a PDF' %}</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</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 id="save-toast" class="toast toast-top toast-end no-print hidden">
|
||||||
<div class="alert alert-success">
|
<div class="alert alert-success">
|
||||||
<span data-message="{% trans 'Guardado' %}">{% trans 'Guardado' %}</span>
|
<span data-message="{% trans 'Guardado' %}">{% trans 'Guardado' %}</span>
|
||||||
@@ -55,10 +45,6 @@
|
|||||||
|
|
||||||
<div class="p-4 pt-0">
|
<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">
|
<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>
|
<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">
|
<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"
|
<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>
|
data-collapse-all-label="{% trans 'Colapsar todo' %}" data-expand-all-label="{% trans 'Expandir todo' %}">{% trans 'Colapsar todo' %}</button>
|
||||||
</div>
|
</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">
|
<div class="dropdown mb-4">
|
||||||
<button id="add-section-toggle" type="button" class="btn btn-sm" tabindex="0"
|
<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>
|
data-label="{% trans 'Añadir sección' %}">+ {% trans 'Añadir sección' %}</button>
|
||||||
@@ -195,17 +175,12 @@
|
|||||||
|
|
||||||
</div>
|
</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" }}
|
{{ 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" }}
|
{{ pattern_data|json_script:"pattern-data" }}
|
||||||
</main>
|
</main>
|
||||||
</body>
|
{% endblock %}
|
||||||
<script type="module" src="{% static 'js/main.js' %}"></script>
|
|
||||||
|
|
||||||
</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 %}
|
{% load static i18n %}
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="{{ lang }}">
|
|
||||||
|
|
||||||
<head>
|
{% block title %}{{ page_settings.title|default:"Crochet" }}{% endblock %}
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
{% block extra_css %}
|
||||||
<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). -->
|
|
||||||
<link rel="stylesheet" href="{% static 'css/pattern-detail.css' %}">
|
<link rel="stylesheet" href="{% static 'css/pattern-detail.css' %}">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
@@ -24,19 +12,11 @@
|
|||||||
}
|
}
|
||||||
@page { size: {{ page_settings.pageSize }} {{ page_settings.orientation }}; }
|
@page { size: {{ page_settings.pageSize }} {{ page_settings.orientation }}; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
{% endblock %}
|
||||||
|
|
||||||
<body>
|
{% block content %}
|
||||||
<main class="p-4">
|
<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">
|
<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>
|
<a href="{% url 'crochet:pattern_pdf' pattern.uuid %}" class="detail-btn">{% trans 'Descargar PDF' %}</a>
|
||||||
{% if lang != 'es' %}
|
{% if lang != 'es' %}
|
||||||
{% language 'es' %}
|
{% language 'es' %}
|
||||||
@@ -57,6 +37,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
{% endblock %}
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ dependencies = [
|
|||||||
"django-environ>=0.14.0",
|
"django-environ>=0.14.0",
|
||||||
"django-extensions>=4.1",
|
"django-extensions>=4.1",
|
||||||
"django-filter>=26.1",
|
"django-filter>=26.1",
|
||||||
|
"django-htmx>=1.28.0",
|
||||||
"django-silk>=5.5.0",
|
"django-silk>=5.5.0",
|
||||||
"django-storages>=1.14.6",
|
"django-storages>=1.14.6",
|
||||||
"ipython>=9.15.0",
|
"ipython>=9.15.0",
|
||||||
|
|||||||
@@ -239,6 +239,7 @@ dependencies = [
|
|||||||
{ name = "django-environ" },
|
{ name = "django-environ" },
|
||||||
{ name = "django-extensions" },
|
{ name = "django-extensions" },
|
||||||
{ name = "django-filter" },
|
{ name = "django-filter" },
|
||||||
|
{ name = "django-htmx" },
|
||||||
{ name = "django-silk" },
|
{ name = "django-silk" },
|
||||||
{ name = "django-storages" },
|
{ name = "django-storages" },
|
||||||
{ name = "ipython" },
|
{ name = "ipython" },
|
||||||
@@ -268,6 +269,7 @@ requires-dist = [
|
|||||||
{ name = "django-environ", specifier = ">=0.14.0" },
|
{ name = "django-environ", specifier = ">=0.14.0" },
|
||||||
{ name = "django-extensions", specifier = ">=4.1" },
|
{ name = "django-extensions", specifier = ">=4.1" },
|
||||||
{ name = "django-filter", specifier = ">=26.1" },
|
{ name = "django-filter", specifier = ">=26.1" },
|
||||||
|
{ name = "django-htmx", specifier = ">=1.28.0" },
|
||||||
{ name = "django-silk", specifier = ">=5.5.0" },
|
{ name = "django-silk", specifier = ">=5.5.0" },
|
||||||
{ name = "django-storages", specifier = ">=1.14.6" },
|
{ name = "django-storages", specifier = ">=1.14.6" },
|
||||||
{ name = "ipython", specifier = ">=9.15.0" },
|
{ 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" },
|
{ 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]]
|
[[package]]
|
||||||
name = "django-silk"
|
name = "django-silk"
|
||||||
version = "5.5.0"
|
version = "5.5.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user