# blockenberg/2.0.12/blocks/flashcard/index.js

Blockenberg — 600+ Advanced Gutenberg Blocks &amp; AI Agent for WordPress Block Editor, version 2.0.12. 488 lines.

- Page: https://pluginprobe.com/plugins/blockenberg/2.0.12/code/blocks/flashcard/index.js
- Raw: https://pluginprobe.com/plugins/blockenberg/2.0.12/raw/blocks/flashcard/index.js
- Modified: 2026-08-23T11:58:34+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/blockenberg/2.0.12/code/blocks/flashcard/index.js#L10-L20`.

```javascript
( function () {
    var el                 = wp.element.createElement;
    var useEffect          = wp.element.useEffect;
    var Fragment           = wp.element.Fragment;
    var registerBlockType  = wp.blocks.registerBlockType;
    var __                 = wp.i18n.__;
    var InspectorControls  = wp.blockEditor.InspectorControls;
    var PanelColorSettings = wp.blockEditor.PanelColorSettings;
    var useBlockProps      = wp.blockEditor.useBlockProps;
    var PanelBody          = wp.components.PanelBody;
    var RangeControl       = wp.components.RangeControl;
    var TextControl        = wp.components.TextControl;
    var ToggleControl      = wp.components.ToggleControl;

    var _flTC, _flTV;
    function _tc() { return _flTC || (_flTC = window.bkbgTypographyControl); }
    function _tv() { return _flTV || (_flTV = window.bkbgTypoCssVars); }

    var DEFAULT_CARDS = [
        { id: '1', front: 'What is the capital of France?', back: 'Paris', known: false },
        { id: '2', front: 'What is 7 × 8?', back: '56', known: false },
        { id: '3', front: 'Who wrote Romeo and Juliet?', back: 'William Shakespeare', known: false },
        { id: '4', front: 'What is H₂O?', back: 'Water', known: false },
        { id: '5', front: 'What year did WWII end?', back: '1945', known: false }
    ];

    /**
     * Cards used to be stored as a JSON string. Accept both shapes so older
     * posts keep working and can migrate cleanly.
     */
    function normalizeCards( cards ) {
        if ( Array.isArray( cards ) ) {
            return cards;
        }
        if ( typeof cards === 'string' && cards ) {
            try {
                var parsed = JSON.parse( cards );
                if ( Array.isArray( parsed ) ) {
                    return parsed;
                }
            } catch ( e ) { /* fall through */ }
        }
        return DEFAULT_CARDS.slice();
    }

    /**
     * Only frontend-relevant block attributes — never dump Advanced (bkbg*)
     * attrs or other editor-only noise into data-opts. A full JSON.stringify(a)
     * breaks block validation whenever attributes are added or defaults change.
     */
    function buildFrontendOpts( a ) {
        return {
            title: a.title,
            subtitle: a.subtitle,
            showTitle: a.showTitle,
            showSubtitle: a.showSubtitle,
            cards: normalizeCards( a.cards ),
            showProgress: a.showProgress,
            showShuffleBtn: a.showShuffleBtn,
            showKnownBtn: a.showKnownBtn,
            enableKeyboardShortcuts: a.enableKeyboardShortcuts !== false,
            showKeyboardHint: a.showKeyboardHint,
            cardHeight: a.cardHeight,
            accentColor: a.accentColor,
            cardFrontBg: a.cardFrontBg,
            cardBackBg: a.cardBackBg,
            cardBackColor: a.cardBackColor,
            knownColor: a.knownColor,
            sectionBg: a.sectionBg,
            cardBg: a.cardBg,
            titleColor: a.titleColor,
            subtitleColor: a.subtitleColor,
            labelColor: a.labelColor,
            titleFontSize: a.titleFontSize,
            titleFontWeight: a.titleFontWeight,
            titleLineHeight: a.titleLineHeight,
            subtitleFontSize: a.subtitleFontSize,
            subtitleFontWeight: a.subtitleFontWeight,
            subtitleLineHeight: a.subtitleLineHeight,
            cardFontSize: a.cardFontSize,
            cardFontWeight: a.cardFontWeight,
            cardLineHeight: a.cardLineHeight,
            contentMaxWidth: a.contentMaxWidth,
            typoTitle: a.typoTitle || {},
            typoSubtitle: a.typoSubtitle || {},
            typoCard: a.typoCard || {}
        };
    }

    function saveElement( a ) {
        return el( 'div', useBlockProps.save(),
            el( 'div', {
                className: 'bkbg-fc-app',
                'data-opts': JSON.stringify( buildFrontendOpts( a ) )
            } )
        );
    }

    /**
     * Legacy save: dumped every attribute (including Advanced bkbg* fields).
     * Kept for deprecation matching / migrate path. Still fragile — new saves
     * must never use this.
     */
    function legacySaveAllAttrs( props ) {
        var a = props.attributes;
        var opts = {};
        Object.keys( a ).forEach( function ( key ) {
            // Strip injected Advanced attrs so we can match older dumps that
            // predate / postdate Advanced inconsistently less often.
            if ( key.indexOf( 'bkbg' ) === 0 ) {
                return;
            }
            opts[ key ] = a[ key ];
        } );
        // Legacy posts stored cards as a JSON string inside data-opts.
        if ( Array.isArray( opts.cards ) ) {
            opts.cards = JSON.stringify( opts.cards );
        }
        return el( 'div', useBlockProps.save(),
            el( 'div', {
                className: 'bkbg-fc-app',
                'data-opts': JSON.stringify( opts )
            } )
        );
    }

    registerBlockType( 'blockenberg/flashcard', {
        edit: function ( props ) {
            var a = props.attributes;
            var setAttributes = props.setAttributes;
            var keyboardShortcutsEnabled = a.enableKeyboardShortcuts !== false;
            var wrapStyle = Object.assign( {},
                _tv()( a.typoTitle, '--bkbg-flash-tt-' ),
                _tv()( a.typoSubtitle, '--bkbg-flash-ts-' ),
                _tv()( a.typoCard, '--bkbg-flash-tc-' )
            );
            var blockProps = useBlockProps( { className: 'bkbg-fc-editor-wrap', style: wrapStyle } );

            var cards = normalizeCards( a.cards );

            // One-time migrate: string cards → array attribute.
            useEffect( function () {
                if ( typeof a.cards === 'string' ) {
                    setAttributes( { cards: normalizeCards( a.cards ) } );
                }
            }, [] );

            var containerStyle = {
                background: a.sectionBg,
                borderRadius: '12px',
                padding: '28px',
                maxWidth: a.contentMaxWidth + 'px',
                margin: '0 auto'
            };

            var cardPreviewStyle = {
                background: a.cardFrontBg,
                borderRadius: '12px',
                height: a.cardHeight + 'px',
                display: 'flex',
                alignItems: 'center',
                justifyContent: 'center',
                textAlign: 'center',
                padding: '24px',
                boxShadow: '0 8px 32px rgba(99,102,241,0.15)',
                color: a.labelColor,
                cursor: 'pointer',
                position: 'relative'
            };

            function addCard() {
                setAttributes( {
                    cards: cards.concat( [ { id: Date.now().toString(), front: 'New question', back: 'Answer', known: false } ] )
                } );
            }

            function removeCard( id ) {
                setAttributes( { cards: cards.filter( function ( c ) { return c.id !== id; } ) } );
            }

            function updateCard( id, field, value ) {
                var updated = cards.map( function ( c ) {
                    if ( c.id !== id ) return c;
                    var n = {};
                    Object.keys( c ).forEach( function ( k ) { n[ k ] = c[ k ]; } );
                    n[ field ] = value;
                    return n;
                } );
                setAttributes( { cards: updated } );
            }

            var TC = _tc();

            return el(
                Fragment,
                null,
                el(
                    InspectorControls,
                    null,
                    el(
                        PanelBody,
                        { title: __( 'Content', 'blockenberg' ), initialOpen: true },
                        el( TextControl, {
                            label: __( 'Title', 'blockenberg' ),
                            value: a.title,
                            onChange: function ( v ) { setAttributes( { title: v } ); }
                        } ),
                        el( TextControl, {
                            label: __( 'Subtitle', 'blockenberg' ),
                            value: a.subtitle,
                            onChange: function ( v ) { setAttributes( { subtitle: v } ); }
                        } ),
                        el( ToggleControl, {
                            label: __( 'Show Title', 'blockenberg' ),
                            checked: a.showTitle,
                            onChange: function ( v ) { setAttributes( { showTitle: v } ); },
                            __nextHasNoMarginBottom: true
                        } ),
                        el( ToggleControl, {
                            label: __( 'Show Subtitle', 'blockenberg' ),
                            checked: a.showSubtitle,
                            onChange: function ( v ) { setAttributes( { showSubtitle: v } ); },
                            __nextHasNoMarginBottom: true
                        } )
                    ),
                    el(
                        PanelBody,
                        { title: __( 'Cards', 'blockenberg' ), initialOpen: true },
                        cards.map( function ( card ) {
                            return el(
                                'div',
                                { key: card.id, style: { background: '#f9f9f9', borderRadius: '8px', padding: '12px', marginBottom: '10px', border: '1px solid #e5e7eb' } },
                                el( TextControl, {
                                    label: __( 'Front', 'blockenberg' ),
                                    value: card.front,
                                    onChange: function ( v ) { updateCard( card.id, 'front', v ); }
                                } ),
                                el( TextControl, {
                                    label: __( 'Back', 'blockenberg' ),
                                    value: card.back,
                                    onChange: function ( v ) { updateCard( card.id, 'back', v ); }
                                } ),
                                el(
                                    'button',
                                    {
                                        style: { background: '#fee2e2', color: '#991b1b', border: 'none', borderRadius: '4px', padding: '4px 10px', cursor: 'pointer', fontSize: '12px' },
                                        onClick: function () { removeCard( card.id ); }
                                    },
                                    __( 'Remove', 'blockenberg' )
                                )
                            );
                        } ),
                        el(
                            'button',
                            {
                                style: { background: a.accentColor, color: '#fff', border: 'none', borderRadius: '6px', padding: '8px 16px', cursor: 'pointer', width: '100%', fontWeight: '600' },
                                onClick: addCard
                            },
                            __( '+ Add Card', 'blockenberg' )
                        )
                    ),
                    el(
                        PanelBody,
                        { title: __( 'Options', 'blockenberg' ), initialOpen: false },
                        el( ToggleControl, {
                            label: __( 'Show Progress Bar', 'blockenberg' ),
                            checked: a.showProgress,
                            onChange: function ( v ) { setAttributes( { showProgress: v } ); },
                            __nextHasNoMarginBottom: true
                        } ),
                        el( ToggleControl, {
                            label: __( 'Show Shuffle Button', 'blockenberg' ),
                            checked: a.showShuffleBtn,
                            onChange: function ( v ) { setAttributes( { showShuffleBtn: v } ); },
                            __nextHasNoMarginBottom: true
                        } ),
                        el( ToggleControl, {
                            label: __( 'Show "Mark Known" Button', 'blockenberg' ),
                            checked: a.showKnownBtn,
                            onChange: function ( v ) { setAttributes( { showKnownBtn: v } ); },
                            __nextHasNoMarginBottom: true
                        } ),
                        el( ToggleControl, {
                            label: __( 'Enable keyboard shortcuts', 'blockenberg' ),
                            checked: keyboardShortcutsEnabled,
                            onChange: function ( v ) { setAttributes( { enableKeyboardShortcuts: v } ); },
                            __nextHasNoMarginBottom: true
                        } ),
                        el( ToggleControl, {
                            label: __( 'Show Keyboard Hint', 'blockenberg' ),
                            checked: a.showKeyboardHint,
                            onChange: function ( v ) { setAttributes( { showKeyboardHint: v } ); },
                            __nextHasNoMarginBottom: true
                        } ),
                        el( RangeControl, {
                            label: __( 'Card Height (px)', 'blockenberg' ),
                            value: a.cardHeight,
                            min: 120,
                            max: 500,
                            onChange: function ( v ) { setAttributes( { cardHeight: v } ); }
                        } )
                    ),
                    el(
                        PanelBody,
                        { title: __( 'Typography', 'blockenberg' ), initialOpen: false },
                        TC && el( TC, { label: __( 'Title', 'blockenberg' ), value: a.typoTitle || {}, onChange: function ( v ) { setAttributes( { typoTitle: v } ); } } ),
                        TC && el( TC, { label: __( 'Subtitle', 'blockenberg' ), value: a.typoSubtitle || {}, onChange: function ( v ) { setAttributes( { typoSubtitle: v } ); } } ),
                        TC && el( TC, { label: __( 'Card Text', 'blockenberg' ), value: a.typoCard || {}, onChange: function ( v ) { setAttributes( { typoCard: v } ); } } )
                    ),
                    el(
                        PanelBody,
                        { title: __( 'Appearance', 'blockenberg' ), initialOpen: false },
                        el( RangeControl, {
                            label: __( 'Max Width (px)', 'blockenberg' ),
                            value: a.contentMaxWidth,
                            min: 320,
                            max: 1200,
                            onChange: function ( v ) { setAttributes( { contentMaxWidth: v } ); }
                        } )
                    ),
                    el(
                        PanelColorSettings,
                        {
                            title: __( 'Colors', 'blockenberg' ),
                            initialOpen: false,
                            colorSettings: [
                                { value: a.accentColor,   onChange: function ( v ) { setAttributes( { accentColor: v || '#6366f1' } ); },   label: __( 'Accent Color', 'blockenberg' ) },
                                { value: a.cardFrontBg,   onChange: function ( v ) { setAttributes( { cardFrontBg: v || '#ffffff' } ); },   label: __( 'Card Front Background', 'blockenberg' ) },
                                { value: a.cardBackBg,    onChange: function ( v ) { setAttributes( { cardBackBg: v || '#4f46e5' } ); },    label: __( 'Card Back Background', 'blockenberg' ) },
                                { value: a.cardBackColor, onChange: function ( v ) { setAttributes( { cardBackColor: v || '#ffffff' } ); }, label: __( 'Card Back Text', 'blockenberg' ) },
                                { value: a.knownColor,    onChange: function ( v ) { setAttributes( { knownColor: v || '#22c55e' } ); },    label: __( 'Known Color', 'blockenberg' ) },
                                { value: a.sectionBg,     onChange: function ( v ) { setAttributes( { sectionBg: v || '#f5f3ff' } ); },     label: __( 'Section Background', 'blockenberg' ) },
                                { value: a.cardBg,        onChange: function ( v ) { setAttributes( { cardBg: v || '#ffffff' } ); },        label: __( 'Controls Background', 'blockenberg' ) },
                                { value: a.titleColor,    onChange: function ( v ) { setAttributes( { titleColor: v || '#3730a3' } ); },    label: __( 'Title Color', 'blockenberg' ) },
                                { value: a.subtitleColor, onChange: function ( v ) { setAttributes( { subtitleColor: v || '#6b7280' } ); }, label: __( 'Subtitle Color', 'blockenberg' ) },
                                { value: a.labelColor,    onChange: function ( v ) { setAttributes( { labelColor: v || '#374151' } ); },    label: __( 'Label Color', 'blockenberg' ) }
                            ]
                        }
                    )
                ),
                el(
                    'div',
                    blockProps,
                    el(
                        'div',
                        { style: containerStyle },
                        a.showTitle && el( 'div', {
                            className: 'bkbg-fc-title',
                            style: { color: a.titleColor, marginBottom: '6px' }
                        }, a.title ),
                        a.showSubtitle && el( 'div', {
                            className: 'bkbg-fc-subtitle',
                            style: { color: a.subtitleColor, marginBottom: '20px' }
                        }, a.subtitle ),
                        a.showProgress && el( 'div', {
                            style: { marginBottom: '16px' }
                        },
                            el( 'div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: '13px', color: a.labelColor, marginBottom: '6px' } },
                                el( 'span', null, __( 'Progress', 'blockenberg' ) ),
                                el( 'span', null, '0 / ' + cards.length + ' known' )
                            ),
                            el( 'div', { style: { height: '8px', background: '#e5e7eb', borderRadius: '4px' } },
                                el( 'div', { style: { height: '100%', width: '0%', background: a.accentColor, borderRadius: '4px' } } )
                            )
                        ),
                        el( 'div', { className: 'bkbg-fc-front', style: cardPreviewStyle },
                            el( 'span', null, cards[ 0 ] ? cards[ 0 ].front : 'No cards yet' )
                        ),
                        el( 'div', { style: { display: 'flex', justifyContent: 'center', gap: '12px', marginTop: '16px', flexWrap: 'wrap' } },
                            el( 'button', { style: { background: '#e5e7eb', color: '#374151', border: 'none', borderRadius: '8px', padding: '10px 20px', cursor: 'pointer', fontSize: '20px' } }, '←' ),
                            a.showKnownBtn && el( 'button', { style: { background: a.knownColor, color: '#fff', border: 'none', borderRadius: '8px', padding: '10px 18px', cursor: 'pointer', fontWeight: '600' } }, '✓ Know it' ),
                            el( 'button', { style: { background: '#e5e7eb', color: '#374151', border: 'none', borderRadius: '8px', padding: '10px 20px', cursor: 'pointer', fontSize: '20px' } }, '→' )
                        ),
                        el( 'div', { style: { textAlign: 'center', marginTop: '10px', display: 'flex', gap: '10px', justifyContent: 'center', flexWrap: 'wrap' } },
                            el( 'span', { style: { color: a.subtitleColor, fontSize: '13px' } }, '1 / ' + cards.length ),
                            a.showShuffleBtn && el( 'button', { style: { background: 'transparent', border: '1px solid ' + a.accentColor, color: a.accentColor, borderRadius: '6px', padding: '4px 12px', cursor: 'pointer', fontSize: '12px' } }, '⇌ Shuffle' ),
                            el( 'button', { style: { background: 'transparent', border: '1px solid #e5e7eb', color: a.subtitleColor, borderRadius: '6px', padding: '4px 12px', cursor: 'pointer', fontSize: '12px' } }, '↺ Reset' )
                        ),
                        keyboardShortcutsEnabled && a.showKeyboardHint && el( 'div', { style: { textAlign: 'center', marginTop: '12px', color: a.subtitleColor, fontSize: '12px' } },
                            '← → Arrow keys to navigate • Space to flip'
                        )
                    )
                )
            );
        },

        save: function ( props ) {
            return saveElement( props.attributes );
        },

        deprecated: [
            {
                attributes: {
                    title: { type: 'string', default: 'Flashcard Deck' },
                    subtitle: { type: 'string', default: 'Click a card to reveal the answer' },
                    showTitle: { type: 'boolean', default: true },
                    showSubtitle: { type: 'boolean', default: true },
                    cards: { type: 'string' },
                    showProgress: { type: 'boolean', default: true },
                    showShuffleBtn: { type: 'boolean', default: true },
                    showKnownBtn: { type: 'boolean', default: true },
                    enableKeyboardShortcuts: { type: 'boolean', default: true },
                    showKeyboardHint: { type: 'boolean', default: true },
                    cardHeight: { type: 'number', default: 220 },
                    accentColor: { type: 'string', default: '#6366f1' },
                    cardFrontBg: { type: 'string', default: '#ffffff' },
                    cardBackBg: { type: 'string', default: '#4f46e5' },
                    cardBackColor: { type: 'string', default: '#ffffff' },
                    knownColor: { type: 'string', default: '#22c55e' },
                    sectionBg: { type: 'string', default: '#f5f3ff' },
                    cardBg: { type: 'string', default: '#ffffff' },
                    titleColor: { type: 'string', default: '#3730a3' },
                    subtitleColor: { type: 'string', default: '#6b7280' },
                    labelColor: { type: 'string', default: '#374151' },
                    titleFontSize: { type: 'number', default: 26 },
                    titleFontWeight: { type: 'string', default: '700' },
                    titleLineHeight: { type: 'number', default: 1.2 },
                    subtitleFontSize: { type: 'number', default: 15 },
                    subtitleFontWeight: { type: 'string', default: '400' },
                    subtitleLineHeight: { type: 'number', default: 1.5 },
                    cardFontSize: { type: 'number', default: 18 },
                    cardFontWeight: { type: 'string', default: '500' },
                    cardLineHeight: { type: 'number', default: 1.5 },
                    contentMaxWidth: { type: 'number', default: 660 },
                    typoTitle: { type: 'object', default: {} },
                    typoSubtitle: { type: 'object', default: {} },
                    typoCard: { type: 'object', default: {} }
                },
                isEligible: function ( attributes ) {
                    return typeof attributes.cards === 'string';
                },
                migrate: function ( attributes ) {
                    var next = {};
                    Object.keys( attributes ).forEach( function ( k ) { next[ k ] = attributes[ k ]; } );
                    next.cards = normalizeCards( attributes.cards );
                    return next;
                },
                save: legacySaveAllAttrs
            },
            {
                attributes: {
                    title: { type: 'string', default: 'Flashcard Deck' },
                    subtitle: { type: 'string', default: 'Click a card to reveal the answer' },
                    showTitle: { type: 'boolean', default: true },
                    showSubtitle: { type: 'boolean', default: true },
                    cards: { type: 'array' },
                    showProgress: { type: 'boolean', default: true },
                    showShuffleBtn: { type: 'boolean', default: true },
                    showKnownBtn: { type: 'boolean', default: true },
                    enableKeyboardShortcuts: { type: 'boolean', default: true },
                    showKeyboardHint: { type: 'boolean', default: true },
                    cardHeight: { type: 'number', default: 220 },
                    accentColor: { type: 'string', default: '#6366f1' },
                    cardFrontBg: { type: 'string', default: '#ffffff' },
                    cardBackBg: { type: 'string', default: '#4f46e5' },
                    cardBackColor: { type: 'string', default: '#ffffff' },
                    knownColor: { type: 'string', default: '#22c55e' },
                    sectionBg: { type: 'string', default: '#f5f3ff' },
                    cardBg: { type: 'string', default: '#ffffff' },
                    titleColor: { type: 'string', default: '#3730a3' },
                    subtitleColor: { type: 'string', default: '#6b7280' },
                    labelColor: { type: 'string', default: '#374151' },
                    titleFontSize: { type: 'number', default: 26 },
                    titleFontWeight: { type: 'string', default: '700' },
                    titleLineHeight: { type: 'number', default: 1.2 },
                    subtitleFontSize: { type: 'number', default: 15 },
                    subtitleFontWeight: { type: 'string', default: '400' },
                    subtitleLineHeight: { type: 'number', default: 1.5 },
                    cardFontSize: { type: 'number', default: 18 },
                    cardFontWeight: { type: 'string', default: '500' },
                    cardLineHeight: { type: 'number', default: 1.5 },
                    contentMaxWidth: { type: 'number', default: 660 },
                    typoTitle: { type: 'object', default: {} },
                    typoSubtitle: { type: 'object', default: {} },
                    typoCard: { type: 'object', default: {} }
                },
                migrate: function ( attributes ) {
                    var next = {};
                    Object.keys( attributes ).forEach( function ( k ) { next[ k ] = attributes[ k ]; } );
                    next.cards = normalizeCards( attributes.cards );
                    return next;
                },
                save: legacySaveAllAttrs
            }
        ]
    } );
}() );

```
