import { useEffect, useRef, } from '@wordpress/element' import { useSelect, } from '@wordpress/data' import { useBlockEditContext, store as blockEditorStore, // @ts-expect-error } from '@wordpress/block-editor' import { debugLog, debugWarn, IS_DEV, } from '@ska/utils' import { useTailwindConfigVersion, applyTailwind, } from '../../tailwind' import { useBlocksPresets, getExpiredDynamicPresetsKey, } from '../presets' import { triggerCompiledEvent, } from '../../plugins/options/plugin-icon' import type { TailwindAttributes, } from '../../types' import type { tBlockAttributes, } from '@ska/shared' /** Store clientIds of blocks that are rendered in `core/post-template`. */ const PostTemplateBlocks = new Map() export const useIsPreviewMode = () => { const ctx = useBlockEditContext() if(Object(ctx).hasOwnProperty('isPreviewMode')) { debugWarn('`isPreviewMode` is public.') // API is public, code should be updated. return ctx.isPreviewMode } const symbols = Object.getOwnPropertySymbols(ctx) const isPreviewModeSymbol = symbols.find(s => s.toString().includes('isPreviewMode')) if(!isPreviewModeSymbol) { return false } return ctx[isPreviewModeSymbol] } export const useUpdateBlockInfo = (clientId: string, isInTemplate: boolean, className: string) => { useEffect(() => { /** Update classNames for use in clones. */ if(isInTemplate && PostTemplateBlocks.get(clientId) !== className) { PostTemplateBlocks.set(clientId, className) } }, [isInTemplate, clientId, className]) return null } export const useBlockInfo = (clientId: string, isSelectionEnabled: boolean) => { const { isInTemplate, } = useSelect(select => { const { getBlockParentsByBlockName, } = select(blockEditorStore) return { // In a template in query loop. isInTemplate: getBlockParentsByBlockName(clientId, 'core/post-template').length > 0, } }, [clientId]) const className = PostTemplateBlocks.get(clientId) const isClone = !isInTemplate && className !== undefined // Is a copy in a query loop post template return { /** In a pattern preview, template preview or something like that. */ isPreviewMode: useIsPreviewMode() || !isSelectionEnabled, // `useIsPreviewMode` sometimes lies, but `isSelectionEnabled` seems to be `false` when it does. /** In a (Query loop) Post template block? Only true for the "source" block and not for clones. */ isInTemplate, /** A clone in a (Query loop) Post template block? */ isClone, /** If it is a clone, then the source has these classes, and it should too. */ sourceClassName: className, } } /** * Strip the `editor-preview` selector from block attributes - this selector is used to improve the * appearance of patterns when previewing them, but once the pattern is inserted it has no purpose. */ const withoutEditorPreviewSelector = (attributes: tBlockAttributes): tBlockAttributes => { // Kept in dev mode (when PHP `SKA_DEV` constant is defined) so that it's not lost when manually updating patterns. if(IS_DEV) { return attributes } if(!('skaBlocksSelectors' in attributes)) { return attributes } if(typeof attributes['skaBlocksSelectors'] !== 'object') { return attributes } if('editor-preview' in attributes['skaBlocksSelectors']) { const { 'editor-preview': _, ...restSelectors } = attributes['skaBlocksSelectors'] return { ...attributes, skaBlocksSelectors: { ...restSelectors, }, } } return attributes } /** * Recompile Tailwind CSS when applicable. */ export const useRecompile = (clientId: string, isCandidate: boolean, attributes: tBlockAttributes & TailwindAttributes, setAttributes: (attributes: tBlockAttributes) => void) => { const ver = useTailwindConfigVersion() const presets = useBlocksPresets(attributes, 'dynamic') const lastCompileKeyRef = useRef('') const compileFailedRef = useRef(false) useEffect(() => { /** Don't use recompile. */ if(!isCandidate) { return } /** Compiler not ready yet. */ if(ver < 0) { return } const { skaBlocks = {}, } = attributes const { t = -1, p, } = skaBlocks const expiredVersionKey = t !== ver ? t.toString() : '' const expiredPresetsKey = getExpiredDynamicPresetsKey(p, presets) const compileKey = `${expiredVersionKey}${expiredPresetsKey}` /** Don't need to compile. */ if(!compileKey) { // Since it's no longer need to compile, it's fine that the compile "failed" if(compileFailedRef.current) { compileFailedRef.current = false } return } /** Already compiled this state, but it didn't work or something undid it. */ if(lastCompileKeyRef.current === compileKey) { // Warn when it happens more than once if(compileFailedRef.current) { debugWarn('Compile failed to persist', {clientId, compileKey, expiredVersionKey, expiredPresetsKey, attributes, presets, t, ver}) } compileFailedRef.current = true return // Or potentially infinite loop } lastCompileKeyRef.current = compileKey debugLog('useRecompile compiling', clientId, compileKey, {expiredVersionKey, expiredPresetsKey, attributes, presets, t, ver}) const nextAttributes = applyTailwind(withoutEditorPreviewSelector(attributes), presets) setAttributes(nextAttributes) triggerCompiledEvent() }, [isCandidate, ver, attributes, setAttributes, presets, clientId]) return null }