Fix mobile 3-dots kebab menu in recipe detail view by removing duplicate script block
This commit is contained in:
@@ -368,90 +368,175 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if not is_shared %}
|
{% if not is_shared %}
|
||||||
|
|
||||||
<!-- SHARE MODAL -->
|
<!-- SHARE MODAL -->
|
||||||
|
|
||||||
<div id="share-modal" class="cb-modal-backdrop" onclick="closeShareModal(event)">
|
<div id="share-modal" class="cb-modal-backdrop" onclick="closeShareModal(event)">
|
||||||
|
|
||||||
<div class="cb-modal-card share-modal-card" onclick="event.stopPropagation()">
|
<div class="cb-modal-card share-modal-card" onclick="event.stopPropagation()">
|
||||||
|
|
||||||
<div class="cb-modal-header">
|
<div class="cb-modal-header">
|
||||||
|
|
||||||
<h3 class="cb-modal-title">
|
<h3 class="cb-modal-title">
|
||||||
|
|
||||||
<span>🔗</span> <span>Rezept teilen</span>
|
<span>🔗</span> <span>Rezept teilen</span>
|
||||||
|
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<button type="button" class="cb-modal-close" onclick="closeShareModal()" aria-label="Schließen">×</button>
|
<button type="button" class="cb-modal-close" onclick="closeShareModal()" aria-label="Schließen">×</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="cb-modal-body">
|
<div class="cb-modal-body">
|
||||||
|
|
||||||
<p style="margin-top: 0; color: #4a4a4a; line-height: 1.5; font-size: 0.96rem;">
|
<p style="margin-top: 0; color: #4a4a4a; line-height: 1.5; font-size: 0.96rem;">
|
||||||
|
|
||||||
Erstelle einen Link, um <strong>{{ recipe.title }}</strong> mit Freunden oder Familie zu teilen.
|
Erstelle einen Link, um <strong>{{ recipe.title }}</strong> mit Freunden oder Familie zu teilen.
|
||||||
|
|
||||||
Empfänger können das Rezept direkt ansehen, kochen und ausdrucken – <em>ohne eigenes Login</em>!
|
Empfänger können das Rezept direkt ansehen, kochen und ausdrucken – <em>ohne eigenes Login</em>!
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Neue Freigabe konfigurieren -->
|
<!-- Neue Freigabe konfigurieren -->
|
||||||
|
|
||||||
<div class="share-create-box">
|
<div class="share-create-box">
|
||||||
|
|
||||||
<label class="share-label">Gültigkeitsdauer des Links:</label>
|
<label class="share-label">Gültigkeitsdauer des Links:</label>
|
||||||
|
|
||||||
<div class="share-expiry-pills">
|
<div class="share-expiry-pills">
|
||||||
|
|
||||||
<label class="expiry-pill">
|
<label class="expiry-pill">
|
||||||
|
|
||||||
<input type="radio" name="share_expiry" value="24h">
|
<input type="radio" name="share_expiry" value="24h">
|
||||||
|
|
||||||
<span>24 Stunden</span>
|
<span>24 Stunden</span>
|
||||||
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="expiry-pill">
|
<label class="expiry-pill">
|
||||||
|
|
||||||
<input type="radio" name="share_expiry" value="3d">
|
<input type="radio" name="share_expiry" value="3d">
|
||||||
|
|
||||||
<span>3 Tage</span>
|
<span>3 Tage</span>
|
||||||
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="expiry-pill">
|
<label class="expiry-pill">
|
||||||
|
|
||||||
<input type="radio" name="share_expiry" value="7d" checked>
|
<input type="radio" name="share_expiry" value="7d" checked>
|
||||||
|
|
||||||
<span>7 Tage (Empfohlen)</span>
|
<span>7 Tage (Empfohlen)</span>
|
||||||
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="expiry-pill">
|
<label class="expiry-pill">
|
||||||
|
|
||||||
<input type="radio" name="share_expiry" value="30d">
|
<input type="radio" name="share_expiry" value="30d">
|
||||||
|
|
||||||
<span>30 Tage</span>
|
<span>30 Tage</span>
|
||||||
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="expiry-pill">
|
<label class="expiry-pill">
|
||||||
|
|
||||||
<input type="radio" name="share_expiry" value="unlimited">
|
<input type="radio" name="share_expiry" value="unlimited">
|
||||||
|
|
||||||
<span>Unbegrenzt</span>
|
<span>Unbegrenzt</span>
|
||||||
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="share-input-row" style="margin-top: 14px;">
|
<div class="share-input-row" style="margin-top: 14px;">
|
||||||
|
|
||||||
<input type="text" id="share-label-input" class="share-input" placeholder="Optionale Notiz (z. B. WhatsApp, Familie...)" maxlength="50">
|
<input type="text" id="share-label-input" class="share-input" placeholder="Optionale Notiz (z. B. WhatsApp, Familie...)" maxlength="50">
|
||||||
|
|
||||||
<button type="button" id="btn-create-share" class="cb-btn cb-btn-green" onclick="submitCreateShare()">
|
<button type="button" id="btn-create-share" class="cb-btn cb-btn-green" onclick="submitCreateShare()">
|
||||||
|
|
||||||
<span>✨ Link erstellen</span>
|
<span>✨ Link erstellen</span>
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Neuer erstellter Link (Highlight Box) -->
|
<!-- Neuer erstellter Link (Highlight Box) -->
|
||||||
|
|
||||||
<div id="share-result-box" class="share-result-box" style="display: none;">
|
<div id="share-result-box" class="share-result-box" style="display: none;">
|
||||||
|
|
||||||
<div class="share-result-header">
|
<div class="share-result-header">
|
||||||
|
|
||||||
<span>✓ Neuer Share-Link bereit:</span>
|
<span>✓ Neuer Share-Link bereit:</span>
|
||||||
|
|
||||||
<span id="share-result-expiry-badge" class="share-status-pill share-badge-active"></span>
|
<span id="share-result-expiry-badge" class="share-status-pill share-badge-active"></span>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="share-result-input-group">
|
<div class="share-result-input-group">
|
||||||
|
|
||||||
<input type="text" id="share-result-url" class="share-url-field" readonly onclick="this.select()">
|
<input type="text" id="share-result-url" class="share-url-field" readonly onclick="this.select()">
|
||||||
|
|
||||||
<button type="button" id="btn-copy-share" class="cb-btn cb-btn-green" onclick="copyCurrentShareUrl()">
|
<button type="button" id="btn-copy-share" class="cb-btn cb-btn-green" onclick="copyCurrentShareUrl()">
|
||||||
|
|
||||||
<span id="copy-btn-icon">📋</span> <span id="copy-btn-text">Kopieren</span>
|
<span id="copy-btn-icon">📋</span> <span id="copy-btn-text">Kopieren</span>
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button" id="btn-native-share" class="cb-btn cb-btn-outline" style="display: none;" onclick="triggerNativeShare()">
|
<button type="button" id="btn-native-share" class="cb-btn cb-btn-outline" style="display: none;" onclick="triggerNativeShare()">
|
||||||
|
|
||||||
<span>📤 Teilen</span>
|
<span>📤 Teilen</span>
|
||||||
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="share-result-hint">Jeder mit diesem Link hat für den gewählten Zeitraum vollen Lese- und Kochzugriff.</div>
|
<div class="share-result-hint">Jeder mit diesem Link hat für den gewählten Zeitraum vollen Lese- und Kochzugriff.</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Liste existierender Freigaben -->
|
<!-- Liste existierender Freigaben -->
|
||||||
|
|
||||||
<div class="share-existing-section">
|
<div class="share-existing-section">
|
||||||
|
|
||||||
<h4 class="share-existing-title">Aktive Freigaben für dieses Rezept</h4>
|
<h4 class="share-existing-title">Aktive Freigaben für dieses Rezept</h4>
|
||||||
|
|
||||||
<div id="share-links-list" class="share-links-list">
|
<div id="share-links-list" class="share-links-list">
|
||||||
|
|
||||||
<div class="share-links-loading">Lade aktive Links...</div>
|
<div class="share-links-loading">Lade aktive Links...</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="cb-modal-footer" style="padding: 12px 18px; border-top: 1.5px solid #e0ded9; display: flex; justify-content: flex-end; background: #faf6f0;">
|
<div class="cb-modal-footer" style="padding: 12px 18px; border-top: 1.5px solid #e0ded9; display: flex; justify-content: flex-end; background: #faf6f0;">
|
||||||
|
|
||||||
<button type="button" class="cb-btn cb-btn-outline" onclick="closeShareModal()">Schließen</button>
|
<button type="button" class="cb-btn cb-btn-outline" onclick="closeShareModal()">Schließen</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -602,6 +687,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
{% if not is_shared %}
|
||||||
// SHARE MODAL & FREIGABELINKS
|
// SHARE MODAL & FREIGABELINKS
|
||||||
// ==========================================
|
// ==========================================
|
||||||
let currentShareUrl = '';
|
let currentShareUrl = '';
|
||||||
@@ -792,200 +878,7 @@
|
|||||||
if (!str) return '';
|
if (!str) return '';
|
||||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||||
}
|
}
|
||||||
resUrl.select();
|
{% endif %}
|
||||||
}
|
|
||||||
|
|
||||||
const nativeBtn = document.getElementById('btn-native-share');
|
|
||||||
if (nativeBtn && navigator.share) {
|
|
||||||
nativeBtn.style.display = 'inline-flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (labelInput) labelInput.value = '';
|
|
||||||
showToast('✓ Share-Link erfolgreich erstellt!');
|
|
||||||
loadActiveShareLinks();
|
|
||||||
} catch (err) {
|
|
||||||
showToast('Fehler beim Erstellen des Links');
|
|
||||||
} finally {
|
|
||||||
if (btn) btn.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function copyCurrentShareUrl() {
|
|
||||||
const input = document.getElementById('share-result-url');
|
|
||||||
if (input && input.value) {
|
|
||||||
await copyTextToClipboard(input.value);
|
|
||||||
const copyBtnText = document.getElementById('copy-btn-text');
|
|
||||||
if (copyBtnText) {
|
|
||||||
copyBtnText.textContent = 'Kopiert! ✓';
|
|
||||||
setTimeout(() => { copyBtnText.textContent = 'Kopieren'; }, 2000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function copyTextToClipboard(text, btnEl) {
|
|
||||||
try {
|
|
||||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
||||||
await navigator.clipboard.writeText(text);
|
|
||||||
} else {
|
|
||||||
const ta = document.createElement('textarea');
|
|
||||||
ta.value = text;
|
|
||||||
document.body.appendChild(ta);
|
|
||||||
ta.select();
|
|
||||||
document.execCommand('copy');
|
|
||||||
document.body.removeChild(ta);
|
|
||||||
}
|
|
||||||
showToast('✓ In Zwischenablage kopiert!');
|
|
||||||
if (btnEl) {
|
|
||||||
const orig = btnEl.innerHTML;
|
|
||||||
btnEl.innerHTML = '✓ Kopiert!';
|
|
||||||
setTimeout(() => { btnEl.innerHTML = orig; }, 2000);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
showToast('Konnte Link nicht automatisch kopieren');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function triggerNativeShare() {
|
|
||||||
if (navigator.share && currentShareUrl) {
|
|
||||||
try {
|
|
||||||
await navigator.share({
|
|
||||||
title: 'Rezept: {{ recipe.title }}',
|
|
||||||
text: 'Hier ist das Rezept für {{ recipe.title }}:',
|
|
||||||
url: currentShareUrl
|
|
||||||
});
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteShareLink(shareId) {
|
|
||||||
if (!confirm('Diesen Share-Link wirklich löschen? Empfänger können das Rezept danach nicht mehr über diesen Link aufrufen.')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const resp = await fetch(`/api/recipes/{{ recipe.id }}/shares/${shareId}`, { method: 'DELETE' });
|
|
||||||
if (!resp.ok) throw new Error('Fehler beim Löschen');
|
|
||||||
showToast('Share-Link gelöscht');
|
|
||||||
loadActiveShareLinks();
|
|
||||||
const resBox = document.getElementById('share-result-box');
|
|
||||||
if (resBox) resBox.style.display = 'none';
|
|
||||||
} catch (err) {
|
|
||||||
showToast('Konnte Share-Link nicht löschen');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeHtml(str) {
|
|
||||||
if (!str) return '';
|
|
||||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
|
|
||||||
// SHARE MODAL & FREIGABELINKS
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
|
|
||||||
let currentShareUrl = '';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function openShareModal() {
|
|
||||||
|
|
||||||
const modal = document.getElementById('share-modal');
|
|
||||||
|
|
||||||
if (modal) {
|
|
||||||
|
|
||||||
modal.classList.add('active');
|
|
||||||
|
|
||||||
document.body.classList.add('modal-open');
|
|
||||||
|
|
||||||
loadActiveShareLinks();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const menu = document.getElementById('cb-mobile-menu');
|
|
||||||
|
|
||||||
if (menu) menu.classList.remove('is-open');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function closeShareModal(e) {
|
|
||||||
|
|
||||||
if (e && e.target && e.target.classList && !e.target.classList.contains('cb-modal-backdrop') && !e.target.classList.contains('cb-modal-close') && e.target.tagName !== 'BUTTON') {
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const modal = document.getElementById('share-modal');
|
|
||||||
|
|
||||||
if (modal) {
|
|
||||||
|
|
||||||
modal.classList.remove('active');
|
|
||||||
|
|
||||||
document.body.classList.remove('modal-open');
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function loadActiveShareLinks() {
|
|
||||||
|
|
||||||
const listEl = document.getElementById('share-links-list');
|
|
||||||
|
|
||||||
if (!listEl) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
const resp = await fetch('/api/recipes/{{ recipe.id }}/shares');
|
|
||||||
|
|
||||||
if (!resp.ok) throw new Error('Fehler beim Laden');
|
|
||||||
|
|
||||||
const data = await resp.json();
|
|
||||||
|
|
||||||
renderShareLinks(data.shares || []);
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
|
|
||||||
listEl.innerHTML = '<div class="share-empty-state">Konnte aktive Links nicht laden.</div>';
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function renderShareLinks(shares) {
|
|
||||||
|
|
||||||
const listEl = document.getElementById('share-links-list');
|
|
||||||
|
|
||||||
if (!listEl) return;
|
|
||||||
|
|
||||||
if (!shares || shares.length === 0) {
|
|
||||||
|
|
||||||
listEl.innerHTML = '<div class="share-empty-state">Noch keine Share-Links für dieses Rezept erstellt.</div>';
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
listEl.innerHTML = shares.map(s => {
|
|
||||||
|
|
||||||
const isExp = s.is_expired;
|
|
||||||
|
|
||||||
const statusClass = isExp ? 'share-badge-expired' : 'share-badge-active';
|
|
||||||
|
|
||||||
const labelText = s.label ? `<span class="share-link-label">🏷️ ${escapeHtml(s.label)}</span>` : '';
|
|
||||||
|
|
||||||
const viewsText = `<span class="share-link-views">👁️ ${s.views_count || 0} ${s.views_count === 1 ? 'Aufruf' : 'Aufrufe'}</span>`;
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user