704 lines
19 KiB
HTML
704 lines
19 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Einkaufsliste – What To Cook?</title>
|
||
<link rel="stylesheet" href="/static/cookbook.css?v=4.5.4">
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🛒</text></svg>">
|
||
<style>
|
||
body.shopping-body {
|
||
background-color: var(--grau-bg, #e6e2dc);
|
||
background-image: radial-gradient(rgba(30, 77, 59, 0.06) 1px, transparent 1px);
|
||
background-size: 20px 20px;
|
||
min-height: 100vh;
|
||
margin: 0;
|
||
padding: 0;
|
||
font-family: var(--font-sans);
|
||
color: var(--schwarz);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* Top Nav */
|
||
/* Schlanker, mobiler Top Nav */
|
||
.shop-nav {
|
||
background-color: var(--creme);
|
||
border-bottom: 2px solid var(--schwarz);
|
||
padding: 8px 16px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
box-shadow: 0 3px 8px rgba(0,0,0,0.06);
|
||
}
|
||
|
||
.shop-nav-back {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
background: #ffffff;
|
||
border: 1.5px solid var(--schwarz);
|
||
border-radius: 8px;
|
||
padding: 6px 12px;
|
||
font-size: 0.88rem;
|
||
font-weight: 700;
|
||
color: var(--schwarz);
|
||
text-decoration: none;
|
||
box-shadow: 2px 2px 0px rgba(0,0,0,0.12);
|
||
transition: all 0.15s ease;
|
||
cursor: pointer;
|
||
}
|
||
.shop-nav-back:hover {
|
||
background: rgba(30, 77, 59, 0.08);
|
||
transform: translateY(-1px);
|
||
}
|
||
.shop-nav-back:active {
|
||
transform: translate(1px, 1px);
|
||
box-shadow: none;
|
||
}
|
||
|
||
.shop-nav-center {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.shop-nav-title {
|
||
font-family: var(--font-hand);
|
||
font-size: 1.7rem;
|
||
font-weight: 700;
|
||
color: var(--tannengruen);
|
||
margin: 0;
|
||
line-height: 1;
|
||
}
|
||
|
||
.shop-nav-close {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 34px;
|
||
height: 34px;
|
||
background: #ffffff;
|
||
border: 1.5px solid var(--schwarz);
|
||
border-radius: 50%;
|
||
font-size: 1.15rem;
|
||
font-weight: 700;
|
||
color: #555;
|
||
text-decoration: none;
|
||
box-shadow: 2px 2px 0px rgba(0,0,0,0.1);
|
||
transition: all 0.15s ease;
|
||
}
|
||
.shop-nav-close:hover {
|
||
background: #fdf2f2;
|
||
color: #b00;
|
||
border-color: #b00;
|
||
}
|
||
|
||
/* Hauptbereich Notizblock */
|
||
.shop-main {
|
||
max-width: 720px;
|
||
width: 100%;
|
||
margin: 28px auto 60px auto;
|
||
padding: 0 16px;
|
||
box-sizing: border-box;
|
||
flex: 1;
|
||
}
|
||
|
||
.notepad-card {
|
||
background: #FFFDF7;
|
||
border: 2.5px solid var(--schwarz);
|
||
border-radius: 14px;
|
||
box-shadow: 6px 8px 0px rgba(0,0,0,0.15);
|
||
position: relative;
|
||
overflow: hidden;
|
||
padding: 24px 22px;
|
||
}
|
||
|
||
/* Rote Randlinie wie beim klassischen Notizblock */
|
||
.notepad-card::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0; bottom: 0; left: 44px;
|
||
width: 2px;
|
||
background-color: rgba(210, 155, 155, 0.4);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.notepad-header {
|
||
margin-bottom: 20px;
|
||
padding-bottom: 14px;
|
||
border-bottom: 2px dashed rgba(30, 77, 59, 0.3);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
|
||
.notepad-title-group h2 {
|
||
font-family: var(--font-hand);
|
||
font-size: 2.3rem;
|
||
color: var(--tannengruen);
|
||
margin: 0 0 2px 0;
|
||
line-height: 1.1;
|
||
}
|
||
|
||
.notepad-subtitle {
|
||
font-size: 0.88rem;
|
||
color: #666;
|
||
margin: 0;
|
||
}
|
||
|
||
.notepad-counter {
|
||
font-family: var(--font-hand);
|
||
font-size: 1.15rem;
|
||
font-weight: 700;
|
||
color: var(--tannengruen);
|
||
background: #FFF9D2;
|
||
border: 1.5px dashed #D4C165;
|
||
padding: 4px 10px;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
/* Schnelleingabe */
|
||
.shop-add-form {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin-bottom: 22px;
|
||
background: #FFF9D2;
|
||
border: 2px solid var(--schwarz);
|
||
border-radius: 10px;
|
||
padding: 8px;
|
||
box-shadow: 3px 3px 0px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.shop-input {
|
||
flex: 1;
|
||
border: none;
|
||
background: transparent;
|
||
font-family: var(--font-sans);
|
||
font-size: 1rem;
|
||
padding: 8px 10px;
|
||
outline: none;
|
||
color: var(--schwarz);
|
||
}
|
||
.shop-input::placeholder {
|
||
color: #8c8260;
|
||
}
|
||
|
||
.shop-add-btn {
|
||
background: var(--tannengruen);
|
||
color: var(--creme);
|
||
border: 1.5px solid var(--schwarz);
|
||
border-radius: 6px;
|
||
padding: 8px 16px;
|
||
font-weight: 700;
|
||
font-size: 0.95rem;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
transition: all 0.15s;
|
||
}
|
||
.shop-add-btn:hover {
|
||
background: #27624c;
|
||
}
|
||
|
||
/* Aktionen Toolbar */
|
||
.shop-actions-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px dotted rgba(0,0,0,0.15);
|
||
}
|
||
|
||
.shop-action-btn {
|
||
background: transparent;
|
||
border: 1px solid rgba(0,0,0,0.25);
|
||
border-radius: 6px;
|
||
padding: 4px 8px;
|
||
font-size: 0.8rem;
|
||
color: #555;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
transition: all 0.15s;
|
||
}
|
||
.shop-action-btn:hover {
|
||
background: rgba(0,0,0,0.05);
|
||
color: #222;
|
||
}
|
||
.shop-action-btn.btn-wochenplan {
|
||
background: rgba(30, 77, 59, 0.1);
|
||
border-color: rgba(30, 77, 59, 0.3);
|
||
color: var(--tannengruen);
|
||
font-weight: 600;
|
||
}
|
||
.shop-action-btn.btn-wochenplan:hover {
|
||
background: rgba(30, 77, 59, 0.18);
|
||
}
|
||
|
||
/* Die Einkaufsliste */
|
||
.shopping-items-list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.shopping-item-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 12px;
|
||
padding: 8px 12px 8px 48px;
|
||
border-bottom: 1px solid rgba(0,0,0,0.06);
|
||
transition: background 0.15s;
|
||
border-radius: 6px;
|
||
}
|
||
.shopping-item-row:hover {
|
||
background: rgba(30, 77, 59, 0.03);
|
||
}
|
||
|
||
.shopping-item-row.is-checked {
|
||
opacity: 0.55;
|
||
background: rgba(0, 0, 0, 0.02);
|
||
}
|
||
|
||
.shopping-item-row.is-checked .item-text {
|
||
text-decoration: line-through;
|
||
color: #777;
|
||
}
|
||
|
||
.item-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
flex: 1;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.item-checkbox {
|
||
width: 22px;
|
||
height: 22px;
|
||
border: 2px solid var(--schwarz);
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
accent-color: var(--tannengruen);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.item-text {
|
||
font-size: 1.05rem;
|
||
font-weight: 500;
|
||
color: #222;
|
||
line-height: 1.3;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.item-source-badge {
|
||
font-size: 0.72rem;
|
||
background: rgba(30, 77, 59, 0.08);
|
||
color: var(--tannengruen);
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
margin-left: 6px;
|
||
display: inline-block;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.item-delete-btn {
|
||
background: transparent;
|
||
border: none;
|
||
color: #aaa;
|
||
font-size: 1.1rem;
|
||
cursor: pointer;
|
||
padding: 4px 6px;
|
||
border-radius: 4px;
|
||
transition: color 0.15s;
|
||
}
|
||
.item-delete-btn:hover {
|
||
color: #b00;
|
||
background: rgba(200, 0, 0, 0.08);
|
||
}
|
||
|
||
/* Leerer Zustand */
|
||
.shopping-empty-state {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 3rem;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.empty-title {
|
||
font-family: var(--font-hand);
|
||
font-size: 1.7rem;
|
||
color: var(--tannengruen);
|
||
margin: 0 0 6px 0;
|
||
}
|
||
|
||
.empty-desc {
|
||
color: #666;
|
||
font-size: 0.95rem;
|
||
max-width: 400px;
|
||
margin: 0 auto 18px auto;
|
||
}
|
||
|
||
/* Toast Notification */
|
||
.shop-toast {
|
||
position: fixed;
|
||
bottom: 24px;
|
||
left: 50%;
|
||
transform: translateX(-50%) translateY(100px);
|
||
background: var(--tannengruen);
|
||
color: var(--creme);
|
||
padding: 10px 20px;
|
||
border-radius: 30px;
|
||
font-weight: 600;
|
||
font-size: 0.95rem;
|
||
box-shadow: 0 6px 16px rgba(0,0,0,0.25);
|
||
border: 1.5px solid var(--schwarz);
|
||
z-index: 9999;
|
||
opacity: 0;
|
||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||
pointer-events: none;
|
||
}
|
||
.shop-toast.active {
|
||
transform: translateX(-50%) translateY(0);
|
||
opacity: 1;
|
||
}
|
||
@media (max-width: 600px) {
|
||
.shop-nav {
|
||
padding: 6px 12px;
|
||
}
|
||
.shop-nav-title {
|
||
font-size: 1.4rem;
|
||
}
|
||
.shop-main {
|
||
margin: 10px auto 30px auto;
|
||
padding: 0 8px;
|
||
}
|
||
.notepad-card {
|
||
padding: 14px 10px;
|
||
border-width: 2px;
|
||
border-radius: 10px;
|
||
}
|
||
.notepad-card::before {
|
||
left: 24px;
|
||
}
|
||
.notepad-header {
|
||
margin-bottom: 14px;
|
||
padding-bottom: 10px;
|
||
gap: 6px;
|
||
}
|
||
.notepad-title-group h2 {
|
||
font-size: 1.7rem;
|
||
}
|
||
.notepad-counter {
|
||
font-size: 0.9rem;
|
||
padding: 2px 6px;
|
||
}
|
||
.shop-add-form {
|
||
padding: 6px;
|
||
gap: 6px;
|
||
margin-bottom: 14px;
|
||
}
|
||
.shop-input {
|
||
font-size: 0.9rem;
|
||
padding: 6px 6px;
|
||
}
|
||
.shop-add-btn {
|
||
padding: 6px 10px;
|
||
font-size: 0.85rem;
|
||
}
|
||
.shopping-item-row {
|
||
padding: 8px 8px 8px 30px;
|
||
gap: 8px;
|
||
}
|
||
.item-text {
|
||
font-size: 0.95rem;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="shopping-body">
|
||
|
||
<!-- Schlanker, mobiler Nav-Header -->
|
||
<header class="shop-nav">
|
||
<a href="/" class="shop-nav-back" onclick="if(window.history.length > 1) { event.preventDefault(); window.history.back(); }" title="Zurück zum Menü">
|
||
<span>⬅️</span> <span>Zurück</span>
|
||
</a>
|
||
|
||
<div class="shop-nav-center">
|
||
<span style="font-size: 1.35rem;">🛒</span>
|
||
<h1 class="shop-nav-title">Einkaufsliste</h1>
|
||
</div>
|
||
|
||
<a href="/" class="shop-nav-close" title="Schließen / Zum Menü">✕</a>
|
||
</header>
|
||
|
||
<!-- Main Container -->
|
||
<main class="shop-main">
|
||
<div class="notepad-card">
|
||
|
||
<div class="notepad-header">
|
||
<div class="notepad-title-group">
|
||
<h2>Einkaufszettel</h2>
|
||
<p class="notepad-subtitle">Woche {{ current_week }} • Zutaten & Besorgungen</p>
|
||
</div>
|
||
<div class="notepad-counter" id="shop-counter">
|
||
<span id="checked-count">0</span> / <span id="total-count">{{ items|length }}</span> erledigt
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Schnelleingabe -->
|
||
<form class="shop-add-form" id="shop-add-form" onsubmit="onAddItemSubmit(event)">
|
||
<input
|
||
type="text"
|
||
id="shop-add-input"
|
||
class="shop-input"
|
||
placeholder="✏️ Zutat oder Artikel notieren (z. B. 500g Mehl)..."
|
||
autocomplete="off"
|
||
required
|
||
>
|
||
<button type="submit" class="shop-add-btn">
|
||
<span>➕</span> <span>Hinzufügen</span>
|
||
</button>
|
||
</form>
|
||
|
||
<!-- Toolbar Aktionen -->
|
||
<div class="shop-actions-bar">
|
||
<div>
|
||
<a href="/cookbook?open=mealplan&prompt_transfer=1" class="shop-action-btn btn-wochenplan" title="Zutaten aus dem aktuellen Wochenplan abrufen">
|
||
<span>📅</span> <span>Aus Wochenplan übernehmen</span>
|
||
</a>
|
||
</div>
|
||
<div style="display: flex; gap: 6px;">
|
||
<button type="button" class="shop-action-btn" onclick="deleteCheckedItems()" title="Abgehakte Einträge löschen">
|
||
<span>🧹</span> <span>Erledigte löschen</span>
|
||
</button>
|
||
<button type="button" class="shop-action-btn" onclick="clearAllItems()" title="Ganze Liste leeren">
|
||
<span>🗑️</span> <span>Alles leeren</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Items Liste -->
|
||
<ul class="shopping-items-list" id="shopping-items-list">
|
||
{% for item in items %}
|
||
<li class="shopping-item-row {% if item.checked %}is-checked{% endif %}" id="shop-item-{{ item.id }}" data-id="{{ item.id }}">
|
||
<label class="item-left">
|
||
<input
|
||
type="checkbox"
|
||
class="item-checkbox"
|
||
{% if item.checked %}checked{% endif %}
|
||
onchange="toggleItem({{ item.id }})"
|
||
>
|
||
<span class="item-text">
|
||
{% if item.amount or item.unit %}
|
||
<strong>{{ item.amount }} {{ item.unit }}</strong>
|
||
{% endif %}
|
||
{{ item.item_name }}
|
||
{% if item.source %}
|
||
<span class="item-source-badge">{{ item.source }}</span>
|
||
{% endif %}
|
||
</span>
|
||
</label>
|
||
<button type="button" class="item-delete-btn" onclick="deleteSingleItem({{ item.id }})" title="Löschen">×</button>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
|
||
<!-- Leerer Zustand (wenn 0 Items) -->
|
||
<div class="shopping-empty-state" id="shopping-empty-state" style="{% if items|length > 0 %}display: none;{% endif %}">
|
||
<div class="empty-icon">🎉</div>
|
||
<h3 class="empty-title">Alles eingekauft!</h3>
|
||
<p class="empty-desc">Deine Einkaufsliste ist momentan leer. Füge oben neue Dinge hinzu oder übernimm Zutaten direkt aus deinem Wochenplan.</p>
|
||
<a href="/cookbook?open=mealplan&prompt_transfer=1" class="shop-action-btn btn-wochenplan" style="padding: 10px 18px; font-size: 0.95rem; font-weight: bold;">
|
||
<span>📅</span> <span>Zutaten aus Wochenplan holen</span>
|
||
</a>
|
||
</div>
|
||
|
||
</div>
|
||
</main>
|
||
|
||
<div id="shop-toast" class="shop-toast">✓ Gespeichert</div>
|
||
|
||
<script>
|
||
function showToast(msg) {
|
||
const toast = document.getElementById('shop-toast');
|
||
toast.textContent = msg;
|
||
toast.classList.add('active');
|
||
setTimeout(() => toast.classList.remove('active'), 2500);
|
||
}
|
||
|
||
function updateCounter() {
|
||
const rows = document.querySelectorAll('.shopping-item-row');
|
||
const total = rows.length;
|
||
const checked = document.querySelectorAll('.shopping-item-row.is-checked').length;
|
||
|
||
document.getElementById('checked-count').textContent = checked;
|
||
document.getElementById('total-count').textContent = total;
|
||
|
||
const emptyState = document.getElementById('shopping-empty-state');
|
||
if (total === 0) {
|
||
emptyState.style.display = 'block';
|
||
} else {
|
||
emptyState.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
async function onAddItemSubmit(e) {
|
||
e.preventDefault();
|
||
const input = document.getElementById('shop-add-input');
|
||
const text = input.value.trim();
|
||
if (!text) return;
|
||
|
||
try {
|
||
const res = await fetch('/api/shopping-list', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ item_name: text, source: 'Manuell' })
|
||
});
|
||
const data = await res.json();
|
||
if (data.status === 'ok' && data.item) {
|
||
renderNewItem(data.item);
|
||
input.value = '';
|
||
showToast(`✓ "${text}" hinzugefügt`);
|
||
updateCounter();
|
||
}
|
||
} catch (err) {
|
||
alert('Fehler beim Hinzufügen: ' + err.message);
|
||
}
|
||
}
|
||
|
||
function renderNewItem(item) {
|
||
const ul = document.getElementById('shopping-items-list');
|
||
const li = document.createElement('li');
|
||
li.className = `shopping-item-row ${item.checked ? 'is-checked' : ''}`;
|
||
li.id = `shop-item-${item.id}`;
|
||
li.setAttribute('data-id', item.id);
|
||
|
||
const amountStr = (item.amount || item.unit) ? `<strong>${escapeHtml(item.amount || '')} ${escapeHtml(item.unit || '')}</strong> ` : '';
|
||
const sourceStr = item.source ? `<span class="item-source-badge">${escapeHtml(item.source)}</span>` : '';
|
||
|
||
li.innerHTML = `
|
||
<label class="item-left">
|
||
<input
|
||
type="checkbox"
|
||
class="item-checkbox"
|
||
${item.checked ? 'checked' : ''}
|
||
onchange="toggleItem(${item.id})"
|
||
>
|
||
<span class="item-text">
|
||
${amountStr}${escapeHtml(item.item_name)}${sourceStr}
|
||
</span>
|
||
</label>
|
||
<button type="button" class="item-delete-btn" onclick="deleteSingleItem(${item.id})" title="Löschen">×</button>
|
||
`;
|
||
|
||
// Vor den abgehakten Items einfügen
|
||
const firstChecked = ul.querySelector('.shopping-item-row.is-checked');
|
||
if (firstChecked) {
|
||
ul.insertBefore(li, firstChecked);
|
||
} else {
|
||
ul.appendChild(li);
|
||
}
|
||
}
|
||
|
||
async function toggleItem(id) {
|
||
const row = document.getElementById(`shop-item-${id}`);
|
||
if (!row) return;
|
||
|
||
try {
|
||
const res = await fetch(`/api/shopping-list/${id}/toggle`, { method: 'POST' });
|
||
const data = await res.json();
|
||
if (data.status === 'ok' && data.item) {
|
||
if (data.item.checked) {
|
||
row.classList.add('is-checked');
|
||
// Nach unten schieben
|
||
const ul = document.getElementById('shopping-items-list');
|
||
ul.appendChild(row);
|
||
} else {
|
||
row.classList.remove('is-checked');
|
||
}
|
||
updateCounter();
|
||
}
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
async function deleteSingleItem(id) {
|
||
const row = document.getElementById(`shop-item-${id}`);
|
||
if (row) row.remove();
|
||
updateCounter();
|
||
|
||
try {
|
||
await fetch(`/api/shopping-list/${id}`, { method: 'DELETE' });
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
async function deleteCheckedItems() {
|
||
const checkedRows = document.querySelectorAll('.shopping-item-row.is-checked');
|
||
if (checkedRows.length === 0) {
|
||
showToast('Keine erledigten Einträge vorhanden.');
|
||
return;
|
||
}
|
||
checkedRows.forEach(r => r.remove());
|
||
updateCounter();
|
||
showToast('✓ Erledigte Einträge gelöscht');
|
||
|
||
try {
|
||
await fetch('/api/shopping-list/checked', { method: 'DELETE' });
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
async function clearAllItems() {
|
||
const total = document.querySelectorAll('.shopping-item-row').length;
|
||
if (total === 0) return;
|
||
if (!confirm('Möchtest du wirklich die gesamte Einkaufsliste leeren?')) return;
|
||
|
||
document.getElementById('shopping-items-list').innerHTML = '';
|
||
updateCounter();
|
||
showToast('✓ Einkaufsliste geleert');
|
||
|
||
try {
|
||
await fetch('/api/shopping-list/all', { method: 'DELETE' });
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
function escapeHtml(str) {
|
||
if (!str) return '';
|
||
return String(str)
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', updateCounter);
|
||
</script>
|
||
</body>
|
||
</html>
|