/** * Maple Code Blocks - Gutenberg Block * * Block registration for the Maple Code Blocks plugin */ (function(blocks, element, editor, components, i18n, data, apiFetch) { 'use strict'; const el = element.createElement; const { registerBlockType } = blocks; const { Fragment } = element; const { InspectorControls, BlockControls, AlignmentToolbar, useBlockProps } = editor || {}; // For older WordPress versions, fallback const BlockProps = useBlockProps || function(props) { return props; }; const { PanelBody, PanelRow, TextControl, SelectControl, ToggleControl, Button, Placeholder, Spinner, Notice, ToolbarGroup, ToolbarButton, __experimentalUnitControl: UnitControl, ExternalLink } = components; const { __ } = i18n; const { useState, useEffect } = element; const { useSelect } = data; // Debug: Log that script is loading console.log('Maple Code Blocks: Script loaded, attempting to register block'); // Register the block registerBlockType('maple-code-blocks/code-block', { title: __('Maple Code Block', 'maple-code-blocks'), description: __('Display code from GitHub, GitLab, Bitbucket, or Codeberg repositories', 'maple-code-blocks'), icon: { src: 'editor-code', background: '#0366d6', foreground: '#ffffff' }, category: 'maple-code-blocks', keywords: ['github', 'code', 'repository', 'syntax', 'highlight', 'viewer'], attributes: { repository: { type: 'string', default: '' }, theme: { type: 'string', default: 'dark' }, height: { type: 'string', default: '600px' }, showLineNumbers: { type: 'boolean', default: true }, initialFile: { type: 'string', default: '' }, title: { type: 'string', default: '' }, isValid: { type: 'boolean', default: false }, repoFiles: { type: 'array', default: [] } }, supports: { align: ['wide', 'full'], className: true, customClassName: true, html: false, anchor: true }, edit: function(props) { const { attributes, setAttributes, className, isSelected } = props; const { repository, theme, height, showLineNumbers, initialFile, title, isValid, repoFiles } = attributes; const [isValidating, setIsValidating] = useState(false); const [validationError, setValidationError] = useState(''); const [isLoadingFiles, setIsLoadingFiles] = useState(false); const [popularRepo, setPopularRepo] = useState(''); const blockProps = BlockProps({ className: className + ' mcb-block-editor' }); // Validate repository when it changes useEffect(() => { if (repository && repository.includes('/')) { validateRepository(); } }, [repository]); // Validate repository format and existence const validateRepository = () => { setIsValidating(true); setValidationError(''); // Basic format validation const repoPattern = /^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_\.]+$/; if (!repoPattern.test(repository)) { setValidationError('Invalid format. Use: owner/repository'); setIsValidating(false); setAttributes({ isValid: false }); return; } // Validate with API apiFetch({ path: '/maple-code-blocks/v1/validate-repo', method: 'POST', data: { repository: repository } }).then(response => { setAttributes({ isValid: true }); setValidationError(''); loadRepositoryFiles(); }).catch(error => { setValidationError(error.message || 'Repository not found or inaccessible'); setAttributes({ isValid: false }); }).finally(() => { setIsValidating(false); }); }; // Load repository files for initial file selection const loadRepositoryFiles = () => { setIsLoadingFiles(true); apiFetch({ path: '/maple-code-blocks/v1/get-files', method: 'POST', data: { repository: repository } }).then(response => { setAttributes({ repoFiles: response.files || [] }); }).catch(error => { console.error('Failed to load files:', error); }).finally(() => { setIsLoadingFiles(false); }); }; // Set a popular repository const setPopularRepository = (repo) => { setAttributes({ repository: repo }); setPopularRepo(''); }; // Height units for the control const units = [ { value: 'px', label: 'px' }, { value: '%', label: '%' }, { value: 'vh', label: 'vh' }, { value: 'em', label: 'em' }, { value: 'rem', label: 'rem' } ]; return el(Fragment, {}, // Block Controls Toolbar el(BlockControls, {}, el(ToolbarGroup, {}, el(ToolbarButton, { icon: 'update', label: __('Refresh Repository', 'maple-code-blocks'), onClick: validateRepository, disabled: !repository || isValidating }), el(ToolbarButton, { icon: 'external', label: __('View on GitHub', 'maple-code-blocks'), onClick: () => window.open('https://github.com/' + repository, '_blank'), disabled: !repository || !isValid }) ) ), // Inspector Controls (Sidebar) el(InspectorControls, {}, el(PanelBody, { title: __('Repository Settings', 'maple-code-blocks'), initialOpen: true }, el(TextControl, { label: __('GitHub Repository', 'maple-code-blocks'), value: repository, onChange: (value) => setAttributes({ repository: value }), placeholder: 'owner/repository', help: __('Format: owner/repository (e.g., facebook/react)', 'maple-code-blocks') }), isValidating && el(Spinner), validationError && el(Notice, { status: 'error', isDismissible: false }, validationError), isValid && !isValidating && el(Notice, { status: 'success', isDismissible: false }, __('✓ Repository validated', 'maple-code-blocks')), el(PanelRow, {}, el('div', { style: { width: '100%' } }, el('label', {}, __('Popular Repositories', 'maple-code-blocks')), el(SelectControl, { value: popularRepo, onChange: setPopularRepository, options: [ { label: __('Select a repository...', 'maple-code-blocks'), value: '' }, ...mcbBlockData.popularRepos.map(repo => ({ label: repo, value: repo })) ] }) ) ), repository && el(ExternalLink, { href: 'https://github.com/' + repository }, __('View on GitHub →', 'maple-code-blocks')) ), el(PanelBody, { title: __('Display Settings', 'maple-code-blocks'), initialOpen: false }, el(TextControl, { label: __('Title (Optional)', 'maple-code-blocks'), value: title, onChange: (value) => setAttributes({ title: value }), placeholder: __('e.g., React Source Code', 'maple-code-blocks') }), el(SelectControl, { label: __('Theme', 'maple-code-blocks'), value: theme, onChange: (value) => setAttributes({ theme: value }), options: mcbBlockData.themes }), el('div', { className: 'mcb-height-control', style: { marginBottom: '20px' } }, el('label', {}, __('Height', 'maple-code-blocks')), el(TextControl, { value: height, onChange: (value) => setAttributes({ height: value }), placeholder: '600px', help: __('Examples: 600px, 80vh, 100%', 'maple-code-blocks') }) ), el(ToggleControl, { label: __('Show Line Numbers', 'maple-code-blocks'), checked: showLineNumbers, onChange: (value) => setAttributes({ showLineNumbers: value }) }) ), el(PanelBody, { title: __('Advanced Settings', 'maple-code-blocks'), initialOpen: false }, isLoadingFiles && el(Spinner), !isLoadingFiles && repoFiles.length > 0 && el(SelectControl, { label: __('Initial File to Display', 'maple-code-blocks'), value: initialFile, onChange: (value) => setAttributes({ initialFile: value }), options: [ { label: __('None (Show file browser)', 'maple-code-blocks'), value: '' }, ...repoFiles.map(file => ({ label: file.name, value: file.path })) ], help: __('Select a file to display when the viewer loads', 'maple-code-blocks') }) ) ), // Main Block Content el('div', blockProps, !repository ? // Empty state placeholder el(Placeholder, { icon: 'editor-code', label: __('GitHub Code Viewer', 'maple-code-blocks'), instructions: __('Display code from any public GitHub repository', 'maple-code-blocks') }, el(TextControl, { value: repository, onChange: (value) => setAttributes({ repository: value }), placeholder: 'owner/repository', label: __('Repository', 'maple-code-blocks') }), el('div', { style: { marginTop: '10px' } }, el('strong', {}, __('Quick Start:', 'maple-code-blocks')), el('div', { style: { marginTop: '5px' } }, mcbBlockData.popularRepos.slice(0, 3).map(repo => el(Button, { key: repo, isSecondary: true, onClick: () => setAttributes({ repository: repo }), style: { margin: '2px' } }, repo) ) ) ) ) : // Preview state el('div', { className: 'mcb-block-preview', style: { background: theme === 'dark' ? '#1e1e1e' : '#ffffff', border: '1px solid #e0e0e0', borderRadius: '4px', padding: '20px', minHeight: '200px' } }, title && el('h3', { style: { margin: '0 0 10px 0', color: theme === 'dark' ? '#ffffff' : '#000000' } }, title), el('div', { className: 'mcb-preview-header', style: { display: 'flex', alignItems: 'center', marginBottom: '15px', paddingBottom: '10px', borderBottom: '1px solid ' + (theme === 'dark' ? '#444' : '#e0e0e0') } }, el('svg', { width: '20', height: '20', viewBox: '0 0 24 24', style: { marginRight: '10px' } }, el('path', { fill: theme === 'dark' ? '#ffffff' : '#000000', d: 'M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z' }) ), el('span', { style: { fontSize: '14px', color: theme === 'dark' ? '#d4d4d4' : '#666' } }, repository) ), el('div', { className: 'mcb-preview-info', style: { display: 'flex', flexWrap: 'wrap', gap: '15px', fontSize: '13px', color: theme === 'dark' ? '#969696' : '#666' } }, el('div', {}, '🎨 Theme: ' + theme), el('div', {}, '📏 Height: ' + height), el('div', {}, showLineNumbers ? '✅ Line Numbers' : '❌ No Line Numbers'), initialFile && el('div', {}, '📄 Initial: ' + initialFile.split('/').pop()) ), isValidating && el('div', { style: { textAlign: 'center', padding: '20px', color: theme === 'dark' ? '#ffffff' : '#000000' } }, el(Spinner), ' Validating repository...'), !isValidating && isValid && el('div', { style: { marginTop: '15px', padding: '10px', background: theme === 'dark' ? '#0e4429' : '#d4edda', borderRadius: '4px', color: theme === 'dark' ? '#52c41a' : '#155724', fontSize: '13px' } }, '✓ Repository validated and ready to display') ) ) ); }, save: function(props) { // For server-side rendered blocks, we need to return something // This will be replaced by the PHP render callback return el('div', { className: 'maple-code-block-placeholder' }, 'Loading...'); } }); })( window.wp.blocks, window.wp.element, window.wp.blockEditor || window.wp.editor, window.wp.components, window.wp.i18n, window.wp.data, window.wp.apiFetch );