Add tmpl.js

This commit is contained in:
eraden 2024-11-22 21:42:41 +01:00
parent 99fc54eea9
commit 747db9602d
5 changed files with 126 additions and 0 deletions

View File

@ -1349,6 +1349,13 @@ pub async fn health() -> HttpResponse {
HttpResponse::Ok().finish()
}
#[get("/tmpl.js")]
async fn tmpl() -> HttpResponse {
HttpResponse::Ok()
.append_header(("Content-Type", "application/javascript"))
.body(include_str!("../templates/tmpl.js").to_string())
}
pub fn configure(config: &mut actix_web::web::ServiceConfig) {
tmp_cleanup();
@ -1374,5 +1381,6 @@ pub fn configure(config: &mut actix_web::web::ServiceConfig) {
.service(no_ads)
.service(no_sellers)
.service(health)
.service(tmpl)
.service(Files::new("/assets", "./assets"));
}

View File

@ -6,6 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/styles.css">
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
<script type="module" src="/tmpl.js"></script>
<title>Cooked</title>
{% block head %}{% endblock %}
</head>

View File

@ -42,6 +42,10 @@
<textarea id="ingredients" name="ingredients" class="textarea-without-view" required>{{ingredients}}</textarea>
</div>
</label>
{% include "recipies/ingredients-list.jinja" %}
<div class="w-full flex gap-4 flex-wrap">
{% for ingredient in known_ingredients %}
<label class="border rounded-lg p-2 styled-checkbox cursor-pointer">
@ -49,6 +53,9 @@
</label>
{% endfor %}
</div>
<div>
{% include "recipies/ingredients-list.jinja" %}
</div>
<details class="w-full collapse collapse-arrow">
<summary class="text-base font-semibold collapse-title">Ingeredients should be listed as list of AMOUNT UNIT INGEREDIENT. Example:</summary>

View File

@ -0,0 +1,35 @@
<div id="ingredients-list" class="tmpl-wrapper flex flex-col gap-4">
<label for="ingredient" class="flex gap-4">
<div class="flex gap-2 w-full">
<input name="ingredient[][qty]" placeholder="Quantity" type="number" class="input border border-solid border-gray-200 rounded-lg" required/>
<input name="ingredient[][unit]" placeholder="Unit" class="input border border-solid border-gray-200 rounded-lg"/>
<input name="ingredient[][name]" placeholder="Name" class="input border border-solid border-gray-200 rounded-lg" required/>
<input name="ingredient[][sidenote]" placeholder="Sidenote" class="input border border-solid border-gray-200 rounded-lg"/>
<button class="input border border-solid border-gray-200 rounded-lg tmpl-adder tmpl-swap-before" data-tmpl-swap="#remove">
+
</button>
</div>
</label>
<template class="tmpl-row">
<label for="ingredient" class="flex gap-4 tmpl-row">
<div class="flex gap-2 w-full">
<input name="ingredient[][qty]" placeholder="Quantity" type="number" class="input border border-solid border-gray-200 rounded-lg" required/>
<input name="ingredient[][unit]" placeholder="Unit" class="input border border-solid border-gray-200 rounded-lg"/>
<input name="ingredient[][name]" placeholder="Name" class="input border border-solid border-gray-200 rounded-lg" required/>
<input name="ingredient[][sidenote]" placeholder="Sidenote" class="input border border-solid border-gray-200 rounded-lg"/>
<button class="input border border-solid border-gray-200 rounded-lg tmpl-adder tmpl-swap-before" data-tmpl-swap="#remove">
+
</button>
</div>
</label>
</template>
<template class="tmpl-swap" id="remove">
<button class="input border border-solid border-gray-200 rounded-lg tmpl-wrapper-rm" data-tmpl-wrapper-rm="label:has(div > button.tmpl-wrapper-rm)">
-
</button>
</template>
</div>

75
cooked/templates/tmpl.js Normal file
View File

@ -0,0 +1,75 @@
/**
* <template> framework
*
* Usage:
* ```html
* <div class="tmpl-wrapper">
* <input class="tmpl-adder" />
* <template>
* <div>Some content to add</div>
* </template>
* </div>
* ```
*/
const setup_wrapper = (wrapper) => {
const template = wrapper.querySelector("template.tmpl-row");
const adder = wrapper.querySelector(".tmpl-adder");
const receiver = wrapper.querySelector(".tmpl-receiver") || wrapper;
wrapper.addEventListener("click", (ev) => {
if (!ev.target.classList.contains('tmpl-adder')) return;
ev.preventDefault();
ev.stopPropagation();
const content = template.content.cloneNode(true);
template.dispatchEvent(new CustomEvent('tmpl:cloned', { detail: { content } }));
template.dispatchEvent(new CustomEvent('tmpl:before-append', { detail: { content, receiver } }));
Array.from(wrapper.querySelectorAll('.tmpl-rm-before')).forEach(el => el.remove());
receiver.appendChild(content);
template.dispatchEvent(new CustomEvent('tmpl:after-append', { detail: { content, receiver } }));
Array.from(wrapper.querySelectorAll('.tmpl-rm-after')).forEach(el => el.remove());
});
wrapper.addEventListener('click', ev => {
if (!ev.target.classList.contains('tmpl-wrapper-rm')) return;
ev.preventDefault();
ev.stopPropagation();
const selector = ev.target.dataset.tmplWrapperRm;
if (!selector) return;
const child = wrapper.querySelector(selector);
if (!child) return;
child.remove();
});
wrapper.addEventListener('click', ev => {
if (!ev.target.classList.contains('tmpl-swap-before')) return;
ev.preventDefault();
ev.stopPropagation();
const current = ev.target;
const parent = current.parentElement;
if (!parent) return;
const selector = ev.target.dataset.tmplSwap;
if (!selector) return;
const swapTemplate = wrapper.querySelector(`template${selector}`);
if (!swapTemplate) return;
const newNode = swapTemplate.content.cloneNode(true);
parent.replaceChild(newNode, current);
});
};
const mount = () => {
Array.from(document.body.querySelectorAll('.tmpl-wrapper') || []).forEach(wrapper => {
setup_wrapper(wrapper);
});
};
document.addEventListener("DOMContentLoaded", mount);