Anki add-on: ProjectMAnki68 Cloze Enhanced

🎴 ProjectMAnki Cloze Enhanced (Cloze F6 Addon) Das ProjectMAnki Cloze Enhanced Add-on bietet eine erweiterte Steuerung für benutzerdefinierte Lückentexte ( [[Lücke]] ) direkt im Anki-Editor und Reviewer. Mit diesem Add-on kannst du im Editor schnell Lückentexte erstellen, bestehende Lücken entfernen oder jede Zeile einer Karteikarten-Rückseite mit einer eigenen Lücke versehen. ✨ Hauptfeatures 📝 F6 Lückentext-Erstellung: Markiere einen beliebigen Text im Editor und drücke F6 (oder klicke auf das Cloze-Symbol in der Editor-Toolbar), um den Text in [[markierter text]] einzuhüllen. 🧹 Shift+F6 Lücken-Entfernung: Drücke Shift+F6 im Editor, um alle Lückentext-Klammern ( [[ und ]] ) automatisch aus dem Rückseiten-Feld (z. B. Rückseite oder Back ) zu entfernen. Der Lückeninhalt bleibt erhalten. ⚡ Cmd+F6 Zeilen-Lückentext: Drücke Cmd+F6 (oder Ctrl+F6 unter Windows/Linux), um alle bestehenden Lücken aus dem Rückseiten-Feld zu entfernen und stattdessen jede Zeile einzeln in eine eigene Lücke [[...]] einzuhüllen (HTML-Tags und Umbrüche bleiben intakt). 🔄 Native Undo/Redo Unterstützung: Alle Änderungen (Lücke erstellen, Lücken entfernen, Zeilen-Lücken) lassen sich nativ über Ankis globales Rückgängigmachen/Wiederholen ( Cmd+Z / Cmd+Y bzw. das Bearbeiten-Menü) rückgängig machen. 🚀 Schnellstart & Installation Kopiere das Add-on-Verzeichnis in deinen Anki-Addons-Ordner oder installiere die entsprechende .ankiaddon -Datei. Starte Anki neu. Öffne den Editor und nutze die Tastenkombinationen: F6 : Erstellt eine Lücke [[...]] um die Auswahl. Shift+F6 : Entfernt alle Lückentext-Klammern aus dem Rückseiten-Feld. Cmd+F6 (bzw. Ctrl+F6 ): Hüllt jede Zeile des Rückseiten-Felds einzeln in eine Lücke ein. 📋 Karten-Template-Konfiguration Damit die benutzerdefinierten Lückentexte auf deinen Karten gerendert und interaktiv gesteuert werden können, müssen die folgenden Snippets in deinem Notentyp integriert sein. 1. Vorderseite (HTML / Front Template) Füge diesen Indikator und das zugehörige Skript hinzu, um bei aktiven Lückentexten das Badge "LÜCKENTEXT AKTIV" anzuzeigen: <!-- Cloze Aktiv Indikator --> <div id="cloze-indicator" style="display:none; text-align: center; margin-bottom: 0px;"> <br> <span class="cloze-badge"> <br> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" style="margin-right: 5px; vertical-align: middle;"> <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"></path> <path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"></path> </svg> LÜCKENTEXT AKTIV </span> </div> <script> /* Cloze-Indikator einblenden, falls [[ oder ]] auf der Rückseite existieren */ (function() { var backContent = `{{text:Rückseite}}`; var indicator = document.getElementById('cloze-indicator'); if (backContent.indexOf('[[') !== -1 && backContent.indexOf(']]') !== -1 && indicator) { indicator.style.display = 'block'; } })(); </script> 2. Rückseite (HTML / Back Template) Die Rückseite benötigt die Textbox zur Anzeige, das Steuerungs-Pill-Element für die Segmente, ein unsichtbares Input-Feld für den Tastatur-Fokus (Trap) und das Interaktionsskript: <div id="ruckseite-content"> {{Rückseite}} </div> <!-- Cloze Segment-Steuerung --> <div class="cloze-ui-container" id="cloze-ui" style="display:none;"> <div class="cloze-pill"> <div id="cloze-pill-segments" style="display: flex; gap: 6px; align-items: center;"></div> <button class="cloze-next-btn" id="cloze-next-node" title="Next Cloze"> <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"> <path d="m9 18 6-6-6-6"/> </svg> </button> </div> </div> <!-- Input-Trap für Tastatursteuerung --> <input type="text" id="enter-trap" style="position: absolute; opacity: 0; height: 0; width: 0; border: none; pointer-events: none;"> <script> (() => { const container = document.getElementById('ruckseite-content'); const uiContainer = document.getElementById('cloze-ui'); const pillSegments = document.getElementById('cloze-pill-segments'); const nextBtn = document.getElementById('cloze-next-node'); const trap = document.getElementById('enter-trap'); // Spoiler-Elemente, die erst nach dem Aufdecken aller Lücken eingeblendet werden const spoilerElements = document.querySelectorAll('#extra, #button-img, .btn-base, .mcq-section, .lecture-content'); const mainContainer = document.querySelector('.container'); if (!container || !trap) return; window.scrollTo(0, 0); // Lückentexte rendern container.innerHTML = container.innerHTML.replace(/\[\[([\s\S]*?)\]\]/g, '<span class="hidden-cloze"><span>$1</span></span>'); const clozes = Array.from(container.querySelectorAll('.hidden-cloze')); if (clozes.length > 0) { spoilerElements.forEach(el => { el.style.display = 'none'; el.classList.remove('reveal-fade'); }); if (mainContainer) mainContainer.classList.add('cloze-unrevealed-state'); uiContainer.style.display = 'flex'; } else { if (mainContainer) mainContainer.classList.remove('cloze-unrevealed-state'); return; } let revealedCount = 0; pillSegments.innerHTML = clozes.map((_, i) => `<div class="cloze-segment hidden" id="seg-${i}"></div>`).join(''); const smoothScroll = (targetY, duration) => { const startY = window.scrollY || window.pageYOffset; const distance = targetY - startY; let startTime = null; const ease = (t, b, c, d) => (t /= d / 2) < 1 ? c / 2 * t * t + b : -c / 2 * (--t * (t - 2) - 1) + b; const animation = currentTime => { if (!startTime) startTime = currentTime; const timeElapsed = currentTime - startTime; window.scrollTo(0, ease(timeElapsed, startY, distance, duration)); if (timeElapsed < duration) requestAnimationFrame(animation); }; requestAnimationFrame(animation); }; const toggleClozeState = (index, reveal) => { clozes[index].classList.toggle('manual-reveal', reveal); const seg = document.getElementById(`seg-${index}`); if (seg) { seg.classList.toggle('hidden', !reveal); seg.classList.toggle('active', reveal); } }; const grabFocus = () => { if (!('ontouchstart' in window) && navigator.maxTouchPoints === 0 && !trap.disabled) { trap.focus({ preventScroll: true }); } }; const revealNext = () => { if (revealedCount >= clozes.length) return; toggleClozeState(revealedCount, true); revealedCount++; if (revealedCount === clozes.length) { nextBtn.classList.add('fully-revealed'); trap.blur(); trap.disabled = true; if (mainContainer) mainContainer.classList.remove('cloze-unrevealed-state'); spoilerElements.forEach(el => { el.style.display = el.classList.contains('btn-base') ? 'inline-flex' : 'block'; el.classList.add('reveal-fade'); }); window.dispatchEvent(new Event('resize')); setTimeout(() => { const firstReveal = document.getElementById('extra') || document.querySelector('.mcq-section'); if (firstReveal) { const rect = firstReveal.getBoundingClientRect(); if (rect.top > window.innerHeight || rect.bottom < 0) { smoothScroll(rect.top + window.pageYOffset - 40, 800); } } }, 300); } else { grabFocus(); } }; const hidePrevious = () => { if (revealedCount <= 0) return; if (revealedCount === clozes.length) { if (mainContainer) mainContainer.classList.add('cloze-unrevealed-state'); spoilerElements.forEach(el => { el.style.display = 'none'; el.classList.remove('reveal-fade'); }); } revealedCount--; toggleClozeState(revealedCount, false); nextBtn.classList.remove('fully-revealed'); trap.disabled = false; grabFocus(); }; document.addEventListener('click', e => { if (revealedCount < clozes.length && !e.target.closest('a, button, .mcq-option')) grabFocus(); }); document.addEventListener('touchstart', e => { if (!e.target.closest('a, button, .mcq-option, .cloze-next-btn') && revealedCount < clozes.length) { e.preventDefault(); revealNext(); } }, { passive: false }); document.addEventListener('keydown', e => { const isInput = e.target.tagName === 'INPUT' && e.target.id !== 'enter-trap'; if (isInput || e.target.tagName === 'TEXTAREA') return; if (['b', 'e', 'a', 'd'].includes(e.key.toLowerCase())) { trap.blur(); setTimeout(() => { if (revealedCount < clozes.length) grabFocus(); }, 300); return; } if (['Enter', ' ', 'ArrowRight'].includes(e.key) && revealedCount < clozes.length) { e.preventDefault(); e.stopPropagation(); revealNext(); } else if (e.key === 'ArrowLeft' && revealedCount > 0) { e.preventDefault(); hidePrevious(); } else if (revealedCount < clozes.length) { e.preventDefault(); e.stopPropagation(); } }, true); nextBtn.onclick = e => { e.stopPropagation(); revealNext(); }; grabFocus(); })(); </script> 3. Styling (CSS) Füge die folgenden Regeln zu den Kartenstilen (Styling) hinzu: /* ========================================== CLOZE UI STYLES ========================================== */ .hidden-cloze { cursor: pointer; transition: color 0.2s ease, background 0.2s ease; } .nightMode .hidden-cloze { color: #6BC7D3 !important; } .hidden-cloze::before { content: "[...]"; display: inline; font-weight: normal !important; } .hidden-cloze > * { display: none !important; } .hidden-cloze.revealed::before, .hidden-cloze.manual-reveal::before { content: "" !important; display: none; } .hidden-cloze.revealed > *, .hidden-cloze.manual-reveal > * { display: inline !important; font-weight: bold !important; color: MediumSeaGreen !important; } .nightMode .hidden-cloze.revealed b, .nightMode .hidden-cloze.manual-reveal b { color: MediumSeaGreen !important; } .cloze-ui-container { display: flex; align-items: center; justify-content: center; width: 100%; margin-top: 10px; user-select: none; height: 40px; } .cloze-unrevealed-state .cloze-ui-container { margin-bottom: -40px !important; } .cloze-pill { display: inline-flex !important; align-items: center !important; justify-content: center !important; height: 34px; padding: 0px 12px; border: 1.5px solid #2C2C2C; border-radius: 50px; background-color: #0000000A; transition: all 0.35s ease; box-sizing: border-box; } .nightMode .cloze-pill { background: #FFFFFF03; border: 1.5px solid #FFFFFF10; } .cloze-pill:hover { border-color: #3CB3714D; box-shadow: 0 0 12px #3CB37133; transform: scale(1.02); } .cloze-segment { width: 16px; height: 6px; border-radius: 10px; background: #0000001A; transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; } .nightMode .cloze-segment { background: #FFFFFF26; } .cloze-segment:hover { box-shadow: 0 0 5px #3CB37166; transform: translateY(-1px); } .cloze-segment.active { background: MediumSeaGreen; } .cloze-segment.hidden { background: #086F7D; opacity: 0.6; } .cloze-next-btn { display: flex; align-items: center; justify-content: center; background: #086F7D; color: white !important; border-radius: 100%; width: 20px; height: 20px; margin-left: 8px; cursor: pointer; transition: all 0.35s ease; padding: 0; } .cloze-next-btn:hover { background: #0a8a9c; transform: scale(1.1); } .cloze-next-btn.fully-revealed { display: none !important; } .nightMode .cloze-next-btn { background: #6BC7D3; color: #1b1b1b !important; } .cloze-badge { display: inline-flex; align-items: center; padding: 0.4em 1em; border-radius: 999px; font-weight: 700; font-size: 10px; letter-spacing: 0.05em; background-color: #3CB3711A; color: MediumSeaGreen; border: 1px solid #3CB3714D; text-transform: uppercase; user-select: none; } .nightMode .cloze-badge { background-color: #3CB37126; color: #81C784; border-color: #81C7844D; } 📜 Changelog 08.07.2026 v2.0: Undo/Redo & Row Clozing Update: Natives Undo/Redo: Alle Makro-Aktionen (F6, Shift+F6, Cmd+F6) sind jetzt vollständig und nativ über Ankis globales Undo/Redo System rückgängig machbar. Cmd+F6 Zeilen-Lückentext: Neue Shortcut-Funktion, die bestehende Lückentexte der Rückseite entfernt und stattdessen jede Zeile in eine eigene Lücke einhüllt. Version Bump: Versionsnummer auf 2.0 angehoben. #NurLiebe ~Marco