| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | ( function () { |
| 2 | 2 | var el = wp.element.createElement; |
| 3 | - var useState = wp.element.useState; | |
| 3 | + var useEffect = wp.element.useEffect; | |
| 4 | 4 | var Fragment = wp.element.Fragment; |
| 5 | 5 | var registerBlockType = wp.blocks.registerBlockType; |
| 6 | 6 | var __ = wp.i18n.__; |
| 7 | 7 | var InspectorControls = wp.blockEditor.InspectorControls; |
| @@ -8,12 +8,10 @@ | ||
| 8 | 8 | var PanelColorSettings = wp.blockEditor.PanelColorSettings; |
| 9 | 9 | var useBlockProps = wp.blockEditor.useBlockProps; |
| 10 | 10 | var PanelBody = wp.components.PanelBody; |
| 11 | 11 | var RangeControl = wp.components.RangeControl; |
| 12 | - var SelectControl = wp.components.SelectControl; | |
| 13 | 12 | var TextControl = wp.components.TextControl; |
| 14 | 13 | var ToggleControl = wp.components.ToggleControl; |
| 15 | - var Button = wp.components.Button; | |
| 16 | 14 | |
| 17 | 15 | var _flTC, _flTV; |
| 18 | 16 | function _tc() { return _flTC || (_flTC = window.bkbgTypographyControl); } |
| 19 | 17 | function _tv() { return _flTV || (_flTV = window.bkbgTypoCssVars); } |
| @@ -25,23 +23,129 @@ | ||
| 25 | 23 | { id: '4', front: 'What is H₂O?', back: 'Water', known: false }, |
| 26 | 24 | { id: '5', front: 'What year did WWII end?', back: '1945', known: false } |
| 27 | 25 | ]; |
| 28 | 26 | |
| 29 | - registerBlockType('blockenberg/flashcard', { | |
| 30 | - edit: function (props) { | |
| 27 | + /** | |
| 28 | + * Cards used to be stored as a JSON string. Accept both shapes so older | |
| 29 | + * posts keep working and can migrate cleanly. | |
| 30 | + */ | |
| 31 | + function normalizeCards( cards ) { | |
| 32 | + if ( Array.isArray( cards ) ) { | |
| 33 | + return cards; | |
| 34 | + } | |
| 35 | + if ( typeof cards === 'string' && cards ) { | |
| 36 | + try { | |
| 37 | + var parsed = JSON.parse( cards ); | |
| 38 | + if ( Array.isArray( parsed ) ) { | |
| 39 | + return parsed; | |
| 40 | + } | |
| 41 | + } catch ( e ) { /* fall through */ } | |
| 42 | + } | |
| 43 | + return DEFAULT_CARDS.slice(); | |
| 44 | + } | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * Only frontend-relevant block attributes — never dump Advanced (bkbg*) | |
| 48 | + * attrs or other editor-only noise into data-opts. A full JSON.stringify(a) | |
| 49 | + * breaks block validation whenever attributes are added or defaults change. | |
| 50 | + */ | |
| 51 | + function buildFrontendOpts( a ) { | |
| 52 | + return { | |
| 53 | + title: a.title, | |
| 54 | + subtitle: a.subtitle, | |
| 55 | + showTitle: a.showTitle, | |
| 56 | + showSubtitle: a.showSubtitle, | |
| 57 | + cards: normalizeCards( a.cards ), | |
| 58 | + showProgress: a.showProgress, | |
| 59 | + showShuffleBtn: a.showShuffleBtn, | |
| 60 | + showKnownBtn: a.showKnownBtn, | |
| 61 | + enableKeyboardShortcuts: a.enableKeyboardShortcuts !== false, | |
| 62 | + showKeyboardHint: a.showKeyboardHint, | |
| 63 | + cardHeight: a.cardHeight, | |
| 64 | + accentColor: a.accentColor, | |
| 65 | + cardFrontBg: a.cardFrontBg, | |
| 66 | + cardBackBg: a.cardBackBg, | |
| 67 | + cardBackColor: a.cardBackColor, | |
| 68 | + knownColor: a.knownColor, | |
| 69 | + sectionBg: a.sectionBg, | |
| 70 | + cardBg: a.cardBg, | |
| 71 | + titleColor: a.titleColor, | |
| 72 | + subtitleColor: a.subtitleColor, | |
| 73 | + labelColor: a.labelColor, | |
| 74 | + titleFontSize: a.titleFontSize, | |
| 75 | + titleFontWeight: a.titleFontWeight, | |
| 76 | + titleLineHeight: a.titleLineHeight, | |
| 77 | + subtitleFontSize: a.subtitleFontSize, | |
| 78 | + subtitleFontWeight: a.subtitleFontWeight, | |
| 79 | + subtitleLineHeight: a.subtitleLineHeight, | |
| 80 | + cardFontSize: a.cardFontSize, | |
| 81 | + cardFontWeight: a.cardFontWeight, | |
| 82 | + cardLineHeight: a.cardLineHeight, | |
| 83 | + contentMaxWidth: a.contentMaxWidth, | |
| 84 | + typoTitle: a.typoTitle || {}, | |
| 85 | + typoSubtitle: a.typoSubtitle || {}, | |
| 86 | + typoCard: a.typoCard || {} | |
| 87 | + }; | |
| 88 | + } | |
| 89 | + | |
| 90 | + function saveElement( a ) { | |
| 91 | + return el( 'div', useBlockProps.save(), | |
| 92 | + el( 'div', { | |
| 93 | + className: 'bkbg-fc-app', | |
| 94 | + 'data-opts': JSON.stringify( buildFrontendOpts( a ) ) | |
| 95 | + } ) | |
| 96 | + ); | |
| 97 | + } | |
| 98 | + | |
| 99 | + /** | |
| 100 | + * Legacy save: dumped every attribute (including Advanced bkbg* fields). | |
| 101 | + * Kept for deprecation matching / migrate path. Still fragile — new saves | |
| 102 | + * must never use this. | |
| 103 | + */ | |
| 104 | + function legacySaveAllAttrs( props ) { | |
| 105 | + var a = props.attributes; | |
| 106 | + var opts = {}; | |
| 107 | + Object.keys( a ).forEach( function ( key ) { | |
| 108 | + // Strip injected Advanced attrs so we can match older dumps that | |
| 109 | + // predate / postdate Advanced inconsistently less often. | |
| 110 | + if ( key.indexOf( 'bkbg' ) === 0 ) { | |
| 111 | + return; | |
| 112 | + } | |
| 113 | + opts[ key ] = a[ key ]; | |
| 114 | + } ); | |
| 115 | + // Legacy posts stored cards as a JSON string inside data-opts. | |
| 116 | + if ( Array.isArray( opts.cards ) ) { | |
| 117 | + opts.cards = JSON.stringify( opts.cards ); | |
| 118 | + } | |
| 119 | + return el( 'div', useBlockProps.save(), | |
| 120 | + el( 'div', { | |
| 121 | + className: 'bkbg-fc-app', | |
| 122 | + 'data-opts': JSON.stringify( opts ) | |
| 123 | + } ) | |
| 124 | + ); | |
| 125 | + } | |
| 126 | + | |
| 127 | + registerBlockType( 'blockenberg/flashcard', { | |
| 128 | + edit: function ( props ) { | |
| 31 | 129 | var a = props.attributes; |
| 32 | 130 | var setAttributes = props.setAttributes; |
| 33 | 131 | var keyboardShortcutsEnabled = a.enableKeyboardShortcuts !== false; |
| 34 | - var wrapStyle = Object.assign({}, | |
| 35 | - _tv()(a.typoTitle, '--bkbg-flash-tt-'), | |
| 36 | - _tv()(a.typoSubtitle, '--bkbg-flash-ts-'), | |
| 37 | - _tv()(a.typoCard, '--bkbg-flash-tc-') | |
| 132 | + var wrapStyle = Object.assign( {}, | |
| 133 | + _tv()( a.typoTitle, '--bkbg-flash-tt-' ), | |
| 134 | + _tv()( a.typoSubtitle, '--bkbg-flash-ts-' ), | |
| 135 | + _tv()( a.typoCard, '--bkbg-flash-tc-' ) | |
| 38 | 136 | ); |
| 39 | - var blockProps = useBlockProps({ className: 'bkbg-fc-editor-wrap', style: wrapStyle }); | |
| 137 | + var blockProps = useBlockProps( { className: 'bkbg-fc-editor-wrap', style: wrapStyle } ); | |
| 40 | 138 | |
| 41 | - var cards; | |
| 42 | - try { cards = JSON.parse(a.cards); } catch (e) { cards = DEFAULT_CARDS; } | |
| 139 | + var cards = normalizeCards( a.cards ); | |
| 43 | 140 | |
| 141 | + // One-time migrate: string cards → array attribute. | |
| 142 | + useEffect( function () { | |
| 143 | + if ( typeof a.cards === 'string' ) { | |
| 144 | + setAttributes( { cards: normalizeCards( a.cards ) } ); | |
| 145 | + } | |
| 146 | + }, [] ); | |
| 147 | + | |
| 44 | 148 | var containerStyle = { |
| 45 | 149 | background: a.sectionBg, |
| 46 | 150 | borderRadius: '12px', |
| 47 | 151 | padding: '28px', |
| @@ -64,24 +168,30 @@ | ||
| 64 | 168 | position: 'relative' |
| 65 | 169 | }; |
| 66 | 170 | |
| 67 | 171 | function addCard() { |
| 68 | - var newCards = cards.concat([{ id: Date.now().toString(), front: 'New question', back: 'Answer', known: false }]); | |
| 69 | - setAttributes({ cards: JSON.stringify(newCards) }); | |
| 172 | + setAttributes( { | |
| 173 | + cards: cards.concat( [ { id: Date.now().toString(), front: 'New question', back: 'Answer', known: false } ] ) | |
| 174 | + } ); | |
| 70 | 175 | } |
| 71 | 176 | |
| 72 | - function removeCard(id) { | |
| 73 | - setAttributes({ cards: JSON.stringify(cards.filter(function (c) { return c.id !== id; })) }); | |
| 177 | + function removeCard( id ) { | |
| 178 | + setAttributes( { cards: cards.filter( function ( c ) { return c.id !== id; } ) } ); | |
| 74 | 179 | } |
| 75 | 180 | |
| 76 | - function updateCard(id, field, value) { | |
| 77 | - var updated = cards.map(function (c) { | |
| 78 | - if (c.id !== id) return c; | |
| 79 | - var n = {}; Object.keys(c).forEach(function (k) { n[k] = c[k]; }); n[field] = value; return n; | |
| 80 | - }); | |
| 81 | - setAttributes({ cards: JSON.stringify(updated) }); | |
| 181 | + function updateCard( id, field, value ) { | |
| 182 | + var updated = cards.map( function ( c ) { | |
| 183 | + if ( c.id !== id ) return c; | |
| 184 | + var n = {}; | |
| 185 | + Object.keys( c ).forEach( function ( k ) { n[ k ] = c[ k ]; } ); | |
| 186 | + n[ field ] = value; | |
| 187 | + return n; | |
| 188 | + } ); | |
| 189 | + setAttributes( { cards: updated } ); | |
| 82 | 190 | } |
| 83 | 191 | |
| 192 | + var TC = _tc(); | |
| 193 | + | |
| 84 | 194 | return el( |
| 85 | 195 | Fragment, |
| 86 | 196 | null, |
| 87 | 197 | el( |
| @@ -88,59 +198,59 @@ | ||
| 88 | 198 | InspectorControls, |
| 89 | 199 | null, |
| 90 | 200 | el( |
| 91 | 201 | PanelBody, |
| 92 | - { title: __('Content', 'blockenberg'), initialOpen: true }, | |
| 93 | - el(TextControl, { | |
| 94 | - label: __('Title', 'blockenberg'), | |
| 202 | + { title: __( 'Content', 'blockenberg' ), initialOpen: true }, | |
| 203 | + el( TextControl, { | |
| 204 | + label: __( 'Title', 'blockenberg' ), | |
| 95 | 205 | value: a.title, |
| 96 | - onChange: function (v) { setAttributes({ title: v }); } | |
| 97 | - }), | |
| 98 | - el(TextControl, { | |
| 99 | - label: __('Subtitle', 'blockenberg'), | |
| 206 | + onChange: function ( v ) { setAttributes( { title: v } ); } | |
| 207 | + } ), | |
| 208 | + el( TextControl, { | |
| 209 | + label: __( 'Subtitle', 'blockenberg' ), | |
| 100 | 210 | value: a.subtitle, |
| 101 | - onChange: function (v) { setAttributes({ subtitle: v }); } | |
| 102 | - }), | |
| 103 | - el(ToggleControl, { | |
| 104 | - label: __('Show Title', 'blockenberg'), | |
| 211 | + onChange: function ( v ) { setAttributes( { subtitle: v } ); } | |
| 212 | + } ), | |
| 213 | + el( ToggleControl, { | |
| 214 | + label: __( 'Show Title', 'blockenberg' ), | |
| 105 | 215 | checked: a.showTitle, |
| 106 | - onChange: function (v) { setAttributes({ showTitle: v }); }, | |
| 216 | + onChange: function ( v ) { setAttributes( { showTitle: v } ); }, | |
| 107 | 217 | __nextHasNoMarginBottom: true |
| 108 | - }), | |
| 109 | - el(ToggleControl, { | |
| 110 | - label: __('Show Subtitle', 'blockenberg'), | |
| 218 | + } ), | |
| 219 | + el( ToggleControl, { | |
| 220 | + label: __( 'Show Subtitle', 'blockenberg' ), | |
| 111 | 221 | checked: a.showSubtitle, |
| 112 | - onChange: function (v) { setAttributes({ showSubtitle: v }); }, | |
| 222 | + onChange: function ( v ) { setAttributes( { showSubtitle: v } ); }, | |
| 113 | 223 | __nextHasNoMarginBottom: true |
| 114 | - }) | |
| 224 | + } ) | |
| 115 | 225 | ), |
| 116 | 226 | el( |
| 117 | 227 | PanelBody, |
| 118 | - { title: __('Cards', 'blockenberg'), initialOpen: true }, | |
| 119 | - cards.map(function (card) { | |
| 228 | + { title: __( 'Cards', 'blockenberg' ), initialOpen: true }, | |
| 229 | + cards.map( function ( card ) { | |
| 120 | 230 | return el( |
| 121 | 231 | 'div', |
| 122 | 232 | { key: card.id, style: { background: '#f9f9f9', borderRadius: '8px', padding: '12px', marginBottom: '10px', border: '1px solid #e5e7eb' } }, |
| 123 | - el(TextControl, { | |
| 124 | - label: __('Front', 'blockenberg'), | |
| 233 | + el( TextControl, { | |
| 234 | + label: __( 'Front', 'blockenberg' ), | |
| 125 | 235 | value: card.front, |
| 126 | - onChange: function (v) { updateCard(card.id, 'front', v); } | |
| 127 | - }), | |
| 128 | - el(TextControl, { | |
| 129 | - label: __('Back', 'blockenberg'), | |
| 236 | + onChange: function ( v ) { updateCard( card.id, 'front', v ); } | |
| 237 | + } ), | |
| 238 | + el( TextControl, { | |
| 239 | + label: __( 'Back', 'blockenberg' ), | |
| 130 | 240 | value: card.back, |
| 131 | - onChange: function (v) { updateCard(card.id, 'back', v); } | |
| 132 | - }), | |
| 241 | + onChange: function ( v ) { updateCard( card.id, 'back', v ); } | |
| 242 | + } ), | |
| 133 | 243 | el( |
| 134 | 244 | 'button', |
| 135 | 245 | { |
| 136 | 246 | style: { background: '#fee2e2', color: '#991b1b', border: 'none', borderRadius: '4px', padding: '4px 10px', cursor: 'pointer', fontSize: '12px' }, |
| 137 | - onClick: function () { removeCard(card.id); } | |
| 247 | + onClick: function () { removeCard( card.id ); } | |
| 138 | 248 | }, |
| 139 | - __('Remove', 'blockenberg') | |
| 249 | + __( 'Remove', 'blockenberg' ) | |
| 140 | 250 | ) |
| 141 | 251 | ); |
| 142 | - }), | |
| 252 | + } ), | |
| 143 | 253 | el( |
| 144 | 254 | 'button', |
| 145 | 255 | { |
| 146 | 256 | style: { background: a.accentColor, color: '#fff', border: 'none', borderRadius: '6px', padding: '8px 16px', cursor: 'pointer', width: '100%', fontWeight: '600' }, |
| @@ -145,86 +255,86 @@ | ||
| 145 | 255 | { |
| 146 | 256 | style: { background: a.accentColor, color: '#fff', border: 'none', borderRadius: '6px', padding: '8px 16px', cursor: 'pointer', width: '100%', fontWeight: '600' }, |
| 147 | 257 | onClick: addCard |
| 148 | 258 | }, |
| 149 | - __('+ Add Card', 'blockenberg') | |
| 259 | + __( '+ Add Card', 'blockenberg' ) | |
| 150 | 260 | ) |
| 151 | 261 | ), |
| 152 | 262 | el( |
| 153 | 263 | PanelBody, |
| 154 | - { title: __('Options', 'blockenberg'), initialOpen: false }, | |
| 155 | - el(ToggleControl, { | |
| 156 | - label: __('Show Progress Bar', 'blockenberg'), | |
| 264 | + { title: __( 'Options', 'blockenberg' ), initialOpen: false }, | |
| 265 | + el( ToggleControl, { | |
| 266 | + label: __( 'Show Progress Bar', 'blockenberg' ), | |
| 157 | 267 | checked: a.showProgress, |
| 158 | - onChange: function (v) { setAttributes({ showProgress: v }); }, | |
| 268 | + onChange: function ( v ) { setAttributes( { showProgress: v } ); }, | |
| 159 | 269 | __nextHasNoMarginBottom: true |
| 160 | - }), | |
| 161 | - el(ToggleControl, { | |
| 162 | - label: __('Show Shuffle Button', 'blockenberg'), | |
| 270 | + } ), | |
| 271 | + el( ToggleControl, { | |
| 272 | + label: __( 'Show Shuffle Button', 'blockenberg' ), | |
| 163 | 273 | checked: a.showShuffleBtn, |
| 164 | - onChange: function (v) { setAttributes({ showShuffleBtn: v }); }, | |
| 274 | + onChange: function ( v ) { setAttributes( { showShuffleBtn: v } ); }, | |
| 165 | 275 | __nextHasNoMarginBottom: true |
| 166 | - }), | |
| 167 | - el(ToggleControl, { | |
| 168 | - label: __('Show "Mark Known" Button', 'blockenberg'), | |
| 276 | + } ), | |
| 277 | + el( ToggleControl, { | |
| 278 | + label: __( 'Show "Mark Known" Button', 'blockenberg' ), | |
| 169 | 279 | checked: a.showKnownBtn, |
| 170 | - onChange: function (v) { setAttributes({ showKnownBtn: v }); }, | |
| 280 | + onChange: function ( v ) { setAttributes( { showKnownBtn: v } ); }, | |
| 171 | 281 | __nextHasNoMarginBottom: true |
| 172 | - }), | |
| 173 | - el(ToggleControl, { | |
| 174 | - label: __('Enable keyboard shortcuts', 'blockenberg'), | |
| 282 | + } ), | |
| 283 | + el( ToggleControl, { | |
| 284 | + label: __( 'Enable keyboard shortcuts', 'blockenberg' ), | |
| 175 | 285 | checked: keyboardShortcutsEnabled, |
| 176 | - onChange: function (v) { setAttributes({ enableKeyboardShortcuts: v }); }, | |
| 286 | + onChange: function ( v ) { setAttributes( { enableKeyboardShortcuts: v } ); }, | |
| 177 | 287 | __nextHasNoMarginBottom: true |
| 178 | - }), | |
| 179 | - el(ToggleControl, { | |
| 180 | - label: __('Show Keyboard Hint', 'blockenberg'), | |
| 288 | + } ), | |
| 289 | + el( ToggleControl, { | |
| 290 | + label: __( 'Show Keyboard Hint', 'blockenberg' ), | |
| 181 | 291 | checked: a.showKeyboardHint, |
| 182 | - onChange: function (v) { setAttributes({ showKeyboardHint: v }); }, | |
| 292 | + onChange: function ( v ) { setAttributes( { showKeyboardHint: v } ); }, | |
| 183 | 293 | __nextHasNoMarginBottom: true |
| 184 | - }), | |
| 185 | - el(RangeControl, { | |
| 186 | - label: __('Card Height (px)', 'blockenberg'), | |
| 294 | + } ), | |
| 295 | + el( RangeControl, { | |
| 296 | + label: __( 'Card Height (px)', 'blockenberg' ), | |
| 187 | 297 | value: a.cardHeight, |
| 188 | 298 | min: 120, |
| 189 | 299 | max: 500, |
| 190 | - onChange: function (v) { setAttributes({ cardHeight: v }); } | |
| 191 | - }) | |
| 300 | + onChange: function ( v ) { setAttributes( { cardHeight: v } ); } | |
| 301 | + } ) | |
| 192 | 302 | ), |
| 193 | 303 | el( |
| 194 | 304 | PanelBody, |
| 195 | - { title: __('Typography', 'blockenberg'), initialOpen: false }, | |
| 196 | - _tc() && _tc()({ label: __('Title', 'blockenberg'), typo: a.typoTitle, onChange: function (v) { setAttributes({ typoTitle: v }); } }), | |
| 197 | - _tc() && _tc()({ label: __('Subtitle', 'blockenberg'), typo: a.typoSubtitle, onChange: function (v) { setAttributes({ typoSubtitle: v }); } }), | |
| 198 | - _tc() && _tc()({ label: __('Card Text', 'blockenberg'), typo: a.typoCard, onChange: function (v) { setAttributes({ typoCard: v }); } }) | |
| 305 | + { title: __( 'Typography', 'blockenberg' ), initialOpen: false }, | |
| 306 | + TC && el( TC, { label: __( 'Title', 'blockenberg' ), value: a.typoTitle || {}, onChange: function ( v ) { setAttributes( { typoTitle: v } ); } } ), | |
| 307 | + TC && el( TC, { label: __( 'Subtitle', 'blockenberg' ), value: a.typoSubtitle || {}, onChange: function ( v ) { setAttributes( { typoSubtitle: v } ); } } ), | |
| 308 | + TC && el( TC, { label: __( 'Card Text', 'blockenberg' ), value: a.typoCard || {}, onChange: function ( v ) { setAttributes( { typoCard: v } ); } } ) | |
| 199 | 309 | ), |
| 200 | 310 | el( |
| 201 | 311 | PanelBody, |
| 202 | - { title: __('Appearance', 'blockenberg'), initialOpen: false }, | |
| 203 | - el(RangeControl, { | |
| 204 | - label: __('Max Width (px)', 'blockenberg'), | |
| 312 | + { title: __( 'Appearance', 'blockenberg' ), initialOpen: false }, | |
| 313 | + el( RangeControl, { | |
| 314 | + label: __( 'Max Width (px)', 'blockenberg' ), | |
| 205 | 315 | value: a.contentMaxWidth, |
| 206 | 316 | min: 320, |
| 207 | 317 | max: 1200, |
| 208 | - onChange: function (v) { setAttributes({ contentMaxWidth: v }); } | |
| 209 | - }) | |
| 318 | + onChange: function ( v ) { setAttributes( { contentMaxWidth: v } ); } | |
| 319 | + } ) | |
| 210 | 320 | ), |
| 211 | 321 | el( |
| 212 | 322 | PanelColorSettings, |
| 213 | 323 | { |
| 214 | - title: __('Colors', 'blockenberg'), | |
| 324 | + title: __( 'Colors', 'blockenberg' ), | |
| 215 | 325 | initialOpen: false, |
| 216 | 326 | colorSettings: [ |
| 217 | - { value: a.accentColor, onChange: function (v) { setAttributes({ accentColor: v || '#6366f1' }); }, label: __('Accent Color', 'blockenberg') }, | |
| 218 | - { value: a.cardFrontBg, onChange: function (v) { setAttributes({ cardFrontBg: v || '#ffffff' }); }, label: __('Card Front Background', 'blockenberg') }, | |
| 219 | - { value: a.cardBackBg, onChange: function (v) { setAttributes({ cardBackBg: v || '#4f46e5' }); }, label: __('Card Back Background', 'blockenberg') }, | |
| 220 | - { value: a.cardBackColor, onChange: function (v) { setAttributes({ cardBackColor: v || '#ffffff' }); }, label: __('Card Back Text', 'blockenberg') }, | |
| 221 | - { value: a.knownColor, onChange: function (v) { setAttributes({ knownColor: v || '#22c55e' }); }, label: __('Known Color', 'blockenberg') }, | |
| 222 | - { value: a.sectionBg, onChange: function (v) { setAttributes({ sectionBg: v || '#f5f3ff' }); }, label: __('Section Background', 'blockenberg') }, | |
| 223 | - { value: a.cardBg, onChange: function (v) { setAttributes({ cardBg: v || '#ffffff' }); }, label: __('Controls Background', 'blockenberg') }, | |
| 224 | - { value: a.titleColor, onChange: function (v) { setAttributes({ titleColor: v || '#3730a3' }); }, label: __('Title Color', 'blockenberg') }, | |
| 225 | - { value: a.subtitleColor, onChange: function (v) { setAttributes({ subtitleColor: v || '#6b7280' }); }, label: __('Subtitle Color', 'blockenberg') }, | |
| 226 | - { value: a.labelColor, onChange: function (v) { setAttributes({ labelColor: v || '#374151' }); }, label: __('Label Color', 'blockenberg') } | |
| 327 | + { value: a.accentColor, onChange: function ( v ) { setAttributes( { accentColor: v || '#6366f1' } ); }, label: __( 'Accent Color', 'blockenberg' ) }, | |
| 328 | + { value: a.cardFrontBg, onChange: function ( v ) { setAttributes( { cardFrontBg: v || '#ffffff' } ); }, label: __( 'Card Front Background', 'blockenberg' ) }, | |
| 329 | + { value: a.cardBackBg, onChange: function ( v ) { setAttributes( { cardBackBg: v || '#4f46e5' } ); }, label: __( 'Card Back Background', 'blockenberg' ) }, | |
| 330 | + { value: a.cardBackColor, onChange: function ( v ) { setAttributes( { cardBackColor: v || '#ffffff' } ); }, label: __( 'Card Back Text', 'blockenberg' ) }, | |
| 331 | + { value: a.knownColor, onChange: function ( v ) { setAttributes( { knownColor: v || '#22c55e' } ); }, label: __( 'Known Color', 'blockenberg' ) }, | |
| 332 | + { value: a.sectionBg, onChange: function ( v ) { setAttributes( { sectionBg: v || '#f5f3ff' } ); }, label: __( 'Section Background', 'blockenberg' ) }, | |
| 333 | + { value: a.cardBg, onChange: function ( v ) { setAttributes( { cardBg: v || '#ffffff' } ); }, label: __( 'Controls Background', 'blockenberg' ) }, | |
| 334 | + { value: a.titleColor, onChange: function ( v ) { setAttributes( { titleColor: v || '#3730a3' } ); }, label: __( 'Title Color', 'blockenberg' ) }, | |
| 335 | + { value: a.subtitleColor, onChange: function ( v ) { setAttributes( { subtitleColor: v || '#6b7280' } ); }, label: __( 'Subtitle Color', 'blockenberg' ) }, | |
| 336 | + { value: a.labelColor, onChange: function ( v ) { setAttributes( { labelColor: v || '#374151' } ); }, label: __( 'Label Color', 'blockenberg' ) } | |
| 227 | 337 | ] |
| 228 | 338 | } |
| 229 | 339 | ) |
| 230 | 340 | ), |
| @@ -233,41 +343,41 @@ | ||
| 233 | 343 | blockProps, |
| 234 | 344 | el( |
| 235 | 345 | 'div', |
| 236 | 346 | { style: containerStyle }, |
| 237 | - a.showTitle && el('div', { | |
| 347 | + a.showTitle && el( 'div', { | |
| 238 | 348 | className: 'bkbg-fc-title', |
| 239 | 349 | style: { color: a.titleColor, marginBottom: '6px' } |
| 240 | - }, a.title), | |
| 241 | - a.showSubtitle && el('div', { | |
| 350 | + }, a.title ), | |
| 351 | + a.showSubtitle && el( 'div', { | |
| 242 | 352 | className: 'bkbg-fc-subtitle', |
| 243 | 353 | style: { color: a.subtitleColor, marginBottom: '20px' } |
| 244 | - }, a.subtitle), | |
| 245 | - a.showProgress && el('div', { | |
| 354 | + }, a.subtitle ), | |
| 355 | + a.showProgress && el( 'div', { | |
| 246 | 356 | style: { marginBottom: '16px' } |
| 247 | 357 | }, |
| 248 | - el('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: '13px', color: a.labelColor, marginBottom: '6px' } }, | |
| 249 | - el('span', null, __('Progress', 'blockenberg')), | |
| 250 | - el('span', null, '0 / ' + cards.length + ' known') | |
| 358 | + el( 'div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: '13px', color: a.labelColor, marginBottom: '6px' } }, | |
| 359 | + el( 'span', null, __( 'Progress', 'blockenberg' ) ), | |
| 360 | + el( 'span', null, '0 / ' + cards.length + ' known' ) | |
| 251 | 361 | ), |
| 252 | - el('div', { style: { height: '8px', background: '#e5e7eb', borderRadius: '4px' } }, | |
| 253 | - el('div', { style: { height: '100%', width: '0%', background: a.accentColor, borderRadius: '4px' } }) | |
| 362 | + el( 'div', { style: { height: '8px', background: '#e5e7eb', borderRadius: '4px' } }, | |
| 363 | + el( 'div', { style: { height: '100%', width: '0%', background: a.accentColor, borderRadius: '4px' } } ) | |
| 254 | 364 | ) |
| 255 | 365 | ), |
| 256 | - el('div', { className: 'bkbg-fc-front', style: cardPreviewStyle }, | |
| 257 | - el('span', null, cards[0] ? cards[0].front : 'No cards yet') | |
| 366 | + el( 'div', { className: 'bkbg-fc-front', style: cardPreviewStyle }, | |
| 367 | + el( 'span', null, cards[ 0 ] ? cards[ 0 ].front : 'No cards yet' ) | |
| 258 | 368 | ), |
| 259 | - el('div', { style: { display: 'flex', justifyContent: 'center', gap: '12px', marginTop: '16px', flexWrap: 'wrap' } }, | |
| 260 | - el('button', { style: { background: '#e5e7eb', color: '#374151', border: 'none', borderRadius: '8px', padding: '10px 20px', cursor: 'pointer', fontSize: '20px' } }, '←'), | |
| 261 | - a.showKnownBtn && el('button', { style: { background: a.knownColor, color: '#fff', border: 'none', borderRadius: '8px', padding: '10px 18px', cursor: 'pointer', fontWeight: '600' } }, '✓ Know it'), | |
| 262 | - el('button', { style: { background: '#e5e7eb', color: '#374151', border: 'none', borderRadius: '8px', padding: '10px 20px', cursor: 'pointer', fontSize: '20px' } }, '→') | |
| 369 | + el( 'div', { style: { display: 'flex', justifyContent: 'center', gap: '12px', marginTop: '16px', flexWrap: 'wrap' } }, | |
| 370 | + el( 'button', { style: { background: '#e5e7eb', color: '#374151', border: 'none', borderRadius: '8px', padding: '10px 20px', cursor: 'pointer', fontSize: '20px' } }, '←' ), | |
| 371 | + a.showKnownBtn && el( 'button', { style: { background: a.knownColor, color: '#fff', border: 'none', borderRadius: '8px', padding: '10px 18px', cursor: 'pointer', fontWeight: '600' } }, '✓ Know it' ), | |
| 372 | + el( 'button', { style: { background: '#e5e7eb', color: '#374151', border: 'none', borderRadius: '8px', padding: '10px 20px', cursor: 'pointer', fontSize: '20px' } }, '→' ) | |
| 263 | 373 | ), |
| 264 | - el('div', { style: { textAlign: 'center', marginTop: '10px', display: 'flex', gap: '10px', justifyContent: 'center', flexWrap: 'wrap' } }, | |
| 265 | - el('span', { style: { color: a.subtitleColor, fontSize: '13px' } }, '1 / ' + cards.length), | |
| 266 | - 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'), | |
| 267 | - el('button', { style: { background: 'transparent', border: '1px solid #e5e7eb', color: a.subtitleColor, borderRadius: '6px', padding: '4px 12px', cursor: 'pointer', fontSize: '12px' } }, '↺ Reset') | |
| 374 | + el( 'div', { style: { textAlign: 'center', marginTop: '10px', display: 'flex', gap: '10px', justifyContent: 'center', flexWrap: 'wrap' } }, | |
| 375 | + el( 'span', { style: { color: a.subtitleColor, fontSize: '13px' } }, '1 / ' + cards.length ), | |
| 376 | + 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' ), | |
| 377 | + el( 'button', { style: { background: 'transparent', border: '1px solid #e5e7eb', color: a.subtitleColor, borderRadius: '6px', padding: '4px 12px', cursor: 'pointer', fontSize: '12px' } }, '↺ Reset' ) | |
| 268 | 378 | ), |
| 269 | - keyboardShortcutsEnabled && a.showKeyboardHint && el('div', { style: { textAlign: 'center', marginTop: '12px', color: a.subtitleColor, fontSize: '12px' } }, | |
| 379 | + keyboardShortcutsEnabled && a.showKeyboardHint && el( 'div', { style: { textAlign: 'center', marginTop: '12px', color: a.subtitleColor, fontSize: '12px' } }, | |
| 270 | 380 | '← → Arrow keys to navigate • Space to flip' |
| 271 | 381 | ) |
| 272 | 382 | ) |
| 273 | 383 | ) |
| @@ -272,16 +382,106 @@ | ||
| 272 | 382 | ) |
| 273 | 383 | ) |
| 274 | 384 | ); |
| 275 | 385 | }, |
| 276 | - save: function (props) { | |
| 277 | - var a = props.attributes; | |
| 278 | - var blockProps = wp.blockEditor.useBlockProps.save(); | |
| 279 | - return el('div', blockProps, | |
| 280 | - el('div', { | |
| 281 | - className: 'bkbg-fc-app', | |
| 282 | - 'data-opts': JSON.stringify(a) | |
| 283 | - }) | |
| 284 | - ); | |
| 285 | - } | |
| 286 | - }); | |
| 386 | + | |
| 387 | + save: function ( props ) { | |
| 388 | + return saveElement( props.attributes ); | |
| 389 | + }, | |
| 390 | + | |
| 391 | + deprecated: [ | |
| 392 | + { | |
| 393 | + attributes: { | |
| 394 | + title: { type: 'string', default: 'Flashcard Deck' }, | |
| 395 | + subtitle: { type: 'string', default: 'Click a card to reveal the answer' }, | |
| 396 | + showTitle: { type: 'boolean', default: true }, | |
| 397 | + showSubtitle: { type: 'boolean', default: true }, | |
| 398 | + cards: { type: 'string' }, | |
| 399 | + showProgress: { type: 'boolean', default: true }, | |
| 400 | + showShuffleBtn: { type: 'boolean', default: true }, | |
| 401 | + showKnownBtn: { type: 'boolean', default: true }, | |
| 402 | + enableKeyboardShortcuts: { type: 'boolean', default: true }, | |
| 403 | + showKeyboardHint: { type: 'boolean', default: true }, | |
| 404 | + cardHeight: { type: 'number', default: 220 }, | |
| 405 | + accentColor: { type: 'string', default: '#6366f1' }, | |
| 406 | + cardFrontBg: { type: 'string', default: '#ffffff' }, | |
| 407 | + cardBackBg: { type: 'string', default: '#4f46e5' }, | |
| 408 | + cardBackColor: { type: 'string', default: '#ffffff' }, | |
| 409 | + knownColor: { type: 'string', default: '#22c55e' }, | |
| 410 | + sectionBg: { type: 'string', default: '#f5f3ff' }, | |
| 411 | + cardBg: { type: 'string', default: '#ffffff' }, | |
| 412 | + titleColor: { type: 'string', default: '#3730a3' }, | |
| 413 | + subtitleColor: { type: 'string', default: '#6b7280' }, | |
| 414 | + labelColor: { type: 'string', default: '#374151' }, | |
| 415 | + titleFontSize: { type: 'number', default: 26 }, | |
| 416 | + titleFontWeight: { type: 'string', default: '700' }, | |
| 417 | + titleLineHeight: { type: 'number', default: 1.2 }, | |
| 418 | + subtitleFontSize: { type: 'number', default: 15 }, | |
| 419 | + subtitleFontWeight: { type: 'string', default: '400' }, | |
| 420 | + subtitleLineHeight: { type: 'number', default: 1.5 }, | |
| 421 | + cardFontSize: { type: 'number', default: 18 }, | |
| 422 | + cardFontWeight: { type: 'string', default: '500' }, | |
| 423 | + cardLineHeight: { type: 'number', default: 1.5 }, | |
| 424 | + contentMaxWidth: { type: 'number', default: 660 }, | |
| 425 | + typoTitle: { type: 'object', default: {} }, | |
| 426 | + typoSubtitle: { type: 'object', default: {} }, | |
| 427 | + typoCard: { type: 'object', default: {} } | |
| 428 | + }, | |
| 429 | + isEligible: function ( attributes ) { | |
| 430 | + return typeof attributes.cards === 'string'; | |
| 431 | + }, | |
| 432 | + migrate: function ( attributes ) { | |
| 433 | + var next = {}; | |
| 434 | + Object.keys( attributes ).forEach( function ( k ) { next[ k ] = attributes[ k ]; } ); | |
| 435 | + next.cards = normalizeCards( attributes.cards ); | |
| 436 | + return next; | |
| 437 | + }, | |
| 438 | + save: legacySaveAllAttrs | |
| 439 | + }, | |
| 440 | + { | |
| 441 | + attributes: { | |
| 442 | + title: { type: 'string', default: 'Flashcard Deck' }, | |
| 443 | + subtitle: { type: 'string', default: 'Click a card to reveal the answer' }, | |
| 444 | + showTitle: { type: 'boolean', default: true }, | |
| 445 | + showSubtitle: { type: 'boolean', default: true }, | |
| 446 | + cards: { type: 'array' }, | |
| 447 | + showProgress: { type: 'boolean', default: true }, | |
| 448 | + showShuffleBtn: { type: 'boolean', default: true }, | |
| 449 | + showKnownBtn: { type: 'boolean', default: true }, | |
| 450 | + enableKeyboardShortcuts: { type: 'boolean', default: true }, | |
| 451 | + showKeyboardHint: { type: 'boolean', default: true }, | |
| 452 | + cardHeight: { type: 'number', default: 220 }, | |
| 453 | + accentColor: { type: 'string', default: '#6366f1' }, | |
| 454 | + cardFrontBg: { type: 'string', default: '#ffffff' }, | |
| 455 | + cardBackBg: { type: 'string', default: '#4f46e5' }, | |
| 456 | + cardBackColor: { type: 'string', default: '#ffffff' }, | |
| 457 | + knownColor: { type: 'string', default: '#22c55e' }, | |
| 458 | + sectionBg: { type: 'string', default: '#f5f3ff' }, | |
| 459 | + cardBg: { type: 'string', default: '#ffffff' }, | |
| 460 | + titleColor: { type: 'string', default: '#3730a3' }, | |
| 461 | + subtitleColor: { type: 'string', default: '#6b7280' }, | |
| 462 | + labelColor: { type: 'string', default: '#374151' }, | |
| 463 | + titleFontSize: { type: 'number', default: 26 }, | |
| 464 | + titleFontWeight: { type: 'string', default: '700' }, | |
| 465 | + titleLineHeight: { type: 'number', default: 1.2 }, | |
| 466 | + subtitleFontSize: { type: 'number', default: 15 }, | |
| 467 | + subtitleFontWeight: { type: 'string', default: '400' }, | |
| 468 | + subtitleLineHeight: { type: 'number', default: 1.5 }, | |
| 469 | + cardFontSize: { type: 'number', default: 18 }, | |
| 470 | + cardFontWeight: { type: 'string', default: '500' }, | |
| 471 | + cardLineHeight: { type: 'number', default: 1.5 }, | |
| 472 | + contentMaxWidth: { type: 'number', default: 660 }, | |
| 473 | + typoTitle: { type: 'object', default: {} }, | |
| 474 | + typoSubtitle: { type: 'object', default: {} }, | |
| 475 | + typoCard: { type: 'object', default: {} } | |
| 476 | + }, | |
| 477 | + migrate: function ( attributes ) { | |
| 478 | + var next = {}; | |
| 479 | + Object.keys( attributes ).forEach( function ( k ) { next[ k ] = attributes[ k ]; } ); | |
| 480 | + next.cards = normalizeCards( attributes.cards ); | |
| 481 | + return next; | |
| 482 | + }, | |
| 483 | + save: legacySaveAllAttrs | |
| 484 | + } | |
| 485 | + ] | |
| 486 | + } ); | |
| 287 | 487 | }() ); |