import * as React from 'react' import {__} from '@wordpress/i18n' import cx from 'classnames' import { useEffect, useReducer, RawHTML, } from '@wordpress/element' import { PanelBody, ToggleControl, ToolbarGroup, ToolbarButton, BaseControl, Button, __experimentalHStack as HStack, __experimentalView as View, } from '@wordpress/components' import { useBlockProps, InspectorControls, BlockControls, store as blockEditorStore, // @ts-expect-error } from '@wordpress/block-editor' import { createBlock, // @ts-expect-error } from '@wordpress/blocks' import { useDispatch, } from '@wordpress/data' import { code as codeIcon, } from '@wordpress/icons' import { useQuery, } from '@tanstack/react-query' import { useDebouncedValue, } from '@tanstack/react-pacer' import { CodeEditor, ModalButton, TailwindIcon, UserJsonView, WithTooltip, } from '@ska/components' import { AIButton, useHTMLSystemPrompt, } from '@ska/ai' import { debugLog, } from '@ska/utils' import Preview from './Preview' import { htmlToBlocks, } from './convert' // @ts-ignore import metadata from './block.json' import SkaBlocksInstance from '../../SkaBlocks' import { usePluginOptions, } from '../../plugins/options' import { QueryClientProvider, renderBlock, } from '../../api' import { useTailwindConfigVersion, } from '../../tailwind' import { html_beautify, css_beautify, // @ts-expect-error } from 'js-beautify' const HTML_BEAUTIFY_SETTINGS = { 'indent_size': '1', 'indent_char': '\t', 'max_preserve_newlines': '-1', 'preserve_newlines': false, 'end_with_newline': false, } const CSS_BEAUTIFY_SETTINGS = { 'indent_size': '1', 'indent_char': '\t', 'max_preserve_newlines': '-1', 'preserve_newlines': false, 'end_with_newline': false, } import type { SkaBlocks, } from '../../types' import type { BlockModule, tBlockEditProps, tBlockSaveProps, } from '@ska/shared' import './style.scss' export interface TailwindBlockAttributes { /** HTML user input. */ html: string /** CSS user input. */ customCss: string /** Current tab. */ tab: 'html' | 'css' | 'preview' /** Compiled CSS. */ css: string /** Display HTML input in the editor, disable to display a button and edit contents in a modal. */ inline: boolean /** Server side render the HTML and compile CSS based on that. */ ssr: boolean } const Edit: React.FC> = props => { const { name: blockName, clientId, isSelected, attributes, setAttributes, insertBlocksAfter, } = props const { html: customHtml = '', css = '', customCss = '', tab = 'html', inline = true, ssr = false, } = attributes /** Unescape ampersands. */ const html = customHtml.replaceAll('&', '&') const { htmlToBlocksRemove, } = usePluginOptions() const ver = useTailwindConfigVersion() const canRender = ssr && isSelected const canCompile = ver > -1 && isSelected const [compileKey, incrementCompileKey] = useReducer(i => i + 1, 0) const setTab = (nextTab: TailwindBlockAttributes['tab']) => setAttributes({tab: nextTab}) const { removeBlock, } = useDispatch(blockEditorStore) const blockProps = useBlockProps() const [htmlToRender] = useDebouncedValue(html, { wait: 3000, leading: true, trailing: true, }) const renderQuery = useQuery({ queryKey: ['ssr', compileKey, htmlToRender], queryFn: ({signal}) => { if(!htmlToRender.trim()) { return '' } return renderBlock({ blockName, attributes: {html: htmlToRender}, signal, }) }, placeholderData: htmlToRender, enabled: canRender, retry: 1, gcTime: 1000 * 60 * 10, }) const htmlToCompile = ssr ? (renderQuery.data || '') : html const cssToCompile = customCss const currentContentToCompile = `${htmlToCompile}${cssToCompile}` const [contentToCompile] = useDebouncedValue(currentContentToCompile, { wait: 3000, leading: !ssr, trailing: true, }) const isStale = currentContentToCompile !== contentToCompile const compileQuery = useQuery({ queryKey: ['compile', ver, compileKey, ssr, contentToCompile], queryFn: () => { if(!contentToCompile.trim()) { return '' } return SkaBlocksInstance.compiler.compileHTMLwithCSS(htmlToCompile, cssToCompile, {throwOnError: true}) }, enabled: canCompile && (!ssr || renderQuery.isSuccess), retry: 0, gcTime: 1000 * 60, }) useEffect(() => { if( ver > -1 && typeof compileQuery.data === 'string' && compileQuery.data !== css ) { setAttributes({css: compileQuery.data}) } }, [ver, compileQuery.data, css, setAttributes]) if(isSelected) { debugLog({ blockName, renderQuery: {...renderQuery}, compileQuery: {...compileQuery}, }) } const errors = <> {renderQuery.status === 'error' && renderQuery.failureReason?.message &&

{renderQuery.failureReason.message}

} {compileQuery.status === 'error' && compileQuery.failureReason?.message &&

{compileQuery.failureReason.message}

} const htmlEditor = <> setAttributes({html: nextValue})} /> {errors} const cssEditor = <> setAttributes({customCss: nextValue})} /> {errors} const preview = <> {errors} const tabs = <> setTab('html')} children='HTML' /> setTab('css')} children='CSS' /> setTab('preview')} children={__('Preview', 'ska-blocks')} /> const content = <> {tab === 'html' && htmlEditor} {tab === 'css' && cssEditor} {tab === 'preview' && preview} const isBusy = renderQuery.fetchStatus !== 'idle' || compileQuery.fetchStatus !== 'idle' const compileButton = (