ProjectMAnki68 Cloze Enhanced

AnkiWeb addon 427304797

Adds keyboard shortcuts to create or remove cloze brackets in the editor and an interactive card interface for revealing cloze segments one by one.
AI-generated summary; may contain mistakes.

review-flownote-managementshortcutscard-templatescloze

Open on AnkiWeb Find alternatives

AnkiWeb

Votes
0 (👍 0 · 👎 0)
Updated
2026-07-07
Anki versions
26.05~
License
Not found (this does not mean that the addon has no license)
Virus scan
No ClamAV match A match does not prove malware. No match does not prove that the code is safe.
Languages
Python, JSON
Description language
de

Maintenance

active

  • Last update or commit: 2026-07-07 (117 days before the snapshot).

GitHub

No GitHub repository linked.

Will it work on my Anki?

Version branches
Min AnkiMax AnkiUpdated
2.1.126.05+2026-07-07
History across monthly snapshots

Loading…

Similar addons

Quizify Tool

Renders Markdown in Anki cards as interactive question types—fill-in-the-blank, multiple choice, reveal, annotations, collapsible/tabbed blocks, recitation, audio, code and math—with editor tools and bulk Markdown import.

review-flowformattingcard-templatesclozequizmobile-compatible

2 votes 34% approval 👍 2 · 👎 0 ⭐ 171 Anki 2.1.1~ Updated 2025-02-16
Wrapper meta-addon

Adds configurable editor buttons and keyboard shortcuts for wrapping/unwrapping text, navigating cloze deletions, inserting custom HTML, styling citations and mnemonics, and shuffling images or lists.

formattingshortcutscard-templatescloze

21 votes 55% approval 👍 16 · 👎 5 ⭐ 9 Anki 24.06.2~ Updated 2026-09-14

Description

🎴 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

  1. Kopiere das Add-on-Verzeichnis in deinen Anki-Addons-Ordner oder installiere die entsprechende .ankiaddon-Datei.
  2. Starte Anki neu.
  3. Ö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