🎴 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