/**
* Copyright 2019-2024 Nicolas Jonas
* License: GPL 3.0
*
* Based on: https://gist.github.com/pento/cf38fd73ce0f13fcf0f0ae7d6c4b685d
* Copyright 2019 Gary Pendergast
* License: GPL 2.0+
*/
import json from './block.json';
import './editor.scss';
import { __ } from '@wordpress/i18n';
import ServerSideRender from '@wordpress/server-side-render';
import {
MediaUpload,
MediaUploadCheck,
InspectorControls,
useBlockProps,
} from '@wordpress/block-editor';
import {
BaseControl,
TextControl,
Button,
ToggleControl,
SelectControl,
PanelBody,
ResponsiveWrapper,
DropZone,
} from '@wordpress/components';
import { registerBlockType } from '@wordpress/blocks';
import classnames from 'classnames';
import { Fragment } from '@wordpress/element';
export {};
declare global {
interface Window {
ArveBlockJsBefore: inlineScriptJSON;
Sanitizer?: any;
}
}
interface inlineScriptJSON {
settings: Record< string, OptionProps >;
options: Record< string, boolean | number | string >;
}
interface sectionControls {
main: Array< JSX.Element >;
pro: Array< JSX.Element >;
html5: Array< JSX.Element >;
sticky_videos: Array< JSX.Element >;
random_video: Array< JSX.Element >;
privacy: Array< JSX.Element >;
}
interface SelectOption {
label: string;
value: string;
disabled?: boolean;
}
interface OptionProps {
label: string;
tab: string;
type: string;
description?: string;
placeholder?: string;
options?: Record< string, string >;
ui?: 'image_upload';
ui_element: 'select' | 'input';
ui_element_type: 'text' | 'number' | 'checkbox';
}
const { name } = json;
const { settings, options } = window.ArveBlockJsBefore;
delete settings?.align?.options?.center;
const domParser = new DOMParser();
/**
* Keypair to Gutenberg component
* @param selectOptions
*/
function PrepareSelectOptions(
selectOptions: Record< string, string > | undefined
): Array< SelectOption > {
if ( ! selectOptions ) {
throw new Error( 'no options' );
}
return Object.entries( selectOptions ).map( ( [ key, value ] ) => ( {
label: value,
value: key,
} ) );
}
function changeTextControl( key: string, value: string, props ) {
if ( 'url' === key ) {
const iframe = domParser.parseFromString( value, 'text/html' ).querySelector( 'iframe' );
if ( iframe && iframe.getAttribute( 'src' ) ) {
props.setAttributes( {
[ key ]: iframe.getAttribute( 'src' ),
} );
if ( iframe.width && iframe.height ) {
const ratio = aspectRatio( iframe.width, iframe.height );
if ( '16:9' !== ratio ) {
props.setAttributes( {
aspect_ratio: ratio,
} );
}
}
return;
}
}
props.setAttributes( {
[ key ]: value,
} );
}
const mediaUploadRender = ( open: VoidFunction, val, url: string ): JSX.Element => {
return (
{ /* @ts-ignore */ }
{ !! val && !! url && (
) }
{ ! val && __( 'Set Thumbnail' ) }
);
};
function buildControls( props ) {
const controls = [] as Array< JSX.Element >;
const sectionControls = {} as sectionControls;
const mediaUploadInstructions = (
{ __( 'To edit the featured image, you need permission to upload media.' ) }
);
let selectedMedia;
Object.values( settings ).forEach( ( option: OptionProps ) => {
sectionControls[ option.tab ] = [];
} );
Object.entries( settings ).forEach( ( [ key, option ]: [ string, OptionProps ] ) => {
const val = props.attributes[ key ];
const url = '';
sectionControls[ option.tab ].push(
{ 'select' === option.ui_element && (
{
return props.setAttributes( {
[ key ]: '' === value ? undefined : value,
} );
} }
/>
) }
{ 'checkbox' === option.ui_element_type && (
{
return props.setAttributes( {
[ key ]: value,
} );
} }
/>
) }
{ [ 'text', 'number' ].includes( option.ui_element_type ) && (
{
changeTextControl( key, value, props );
} }
/>
) }
{ 'image_upload' === option.ui && (
{
selectedMedia = media;
return props.setAttributes( {
[ key ]: media.id.toString(),
[ key + '_url' ]: media.url,
} );
} }
allowedTypes={ [ 'image' ] }
modalClass="editor-post-featured-image__media-modal"
render={ ( { open } ) => {
return mediaUploadRender( open, val, url );
} }
value={ val }
/>
{ !! val && !! url && (
{
selectedMedia = media;
return props.setAttributes( {
[ key ]: media.id.toString(),
[ key + '_url' ]: media.url,
} );
} }
allowedTypes={ [ 'image' ] }
modalClass="editor-post-featured-image__media-modal"
render={ ( { open } ) => (
{ __( 'Replace Thumbnail' ) }
) }
/>
) }
{ !! val && (
{
return props.setAttributes( {
[ key ]: '',
[ key + '_url' ]: '',
} );
} }
isDestructive
>
{ __( 'Remove Thumbnail' ) }
) }
) }
);
} );
sectionControls.main.push(
{ __( 'Info', 'advanced-responsive-video-embedder' ) }
);
Object.keys( sectionControls ).forEach( ( key ) => {
controls.push(
{ sectionControls[ key ] }
);
} );
return controls;
// Object.keys( sectionControls ).forEach( ( key ) => {
// controls.push( sectionControls[ key ] );
// open = false;
// } );
// return (
//
// { controls }
//
// );
}
function createHelp( description: string | undefined ): string | JSX.Element {
if ( ! description ) {
return '';
}
const doc = domParser.parseFromString( description, 'text/html' );
const link = doc.querySelector( 'a' );
if ( link ) {
const href = link.getAttribute( 'href' ) || '';
const linkText = link.textContent || '';
description = doc.body.textContent || '';
const textSplit = description.split( linkText );
if ( textSplit.length !== 2 ) {
throw new Error( 'textSplit.length must be 2' );
}
return (
<>
{ textSplit[ 0 ] }
{ linkText }
{ textSplit[ 1 ] }
>
);
}
return description;
}
function capitalizeFirstLetter( str: string ): string {
return str.charAt( 0 ).toUpperCase() + str.slice( 1 );
}
function Edit( props: Record< string, any > ) {
const {
attributes: { mode, align, maxwidth },
} = props;
let pointerEvents = true;
const style = {} as React.CSSProperties;
const attrCopy = JSON.parse( JSON.stringify( props.attributes ) );
delete attrCopy.align;
delete attrCopy.maxwidth;
if ( maxwidth && ( 'left' === align || 'right' === align ) ) {
style.width = '100%';
style.maxWidth = maxwidth;
} else if ( 'left' === align || 'right' === align ) {
style.width = '100%';
style.maxWidth = options.align_maxwidth as string | number;
}
const blockProps = useBlockProps( { style } );
if ( 'normal' === mode || ( ! mode && 'normal' === options.mode ) ) {
pointerEvents = false;
}
return (
<>
{ buildControls( props ) }
>
);
}
// @ts-ignore
registerBlockType( name, {
edit: Edit,
} );
/**
* Calculate aspect ratio based on width and height.
*
* @param {string} width - The width value
* @param {string} height - The height value
* @return {string} The aspect ratio in the format 'width:height'
*/
function aspectRatio( width: string, height: string ): string {
if ( isIntOverZero( width ) && isIntOverZero( height ) ) {
const w = parseInt( width );
const h = parseInt( height );
const arGCD = gcd( w, h );
return w / arGCD + ':' + h / arGCD;
}
return width + ':' + height;
}
/**
* Check if the input string is a positive integer.
*
* @param {string} str - The input string to be checked.
* @return {boolean} Whether the input string is a positive integer or not.
*/
function isIntOverZero( str: string ): boolean {
const n = Math.floor( Number( str ) );
return n !== Infinity && String( n ) === str && n > 0;
}
/**
* Calculates the greatest common divisor of two numbers using the Euclidean algorithm.
*
* @param {number} a - the first number
* @param {number} b - the second number
* @return {number} the greatest common divisor of the two numbers
*/
function gcd( a: number, b: number ): number {
if ( ! b ) {
return a;
}
return gcd( b, a % b );
}
/*
TODO when the sanitizer API
function sanitizeHelpHTML( option: OptionProps ) {
if ( typeof option.description !== 'string' ) {
return '';
}
const div = document.createElement( 'div' );
if ( 'Sanitizer' in window ) {
const sanitizer = new window.Sanitizer( {
allowElements: [ 'a' ],
allowAttributes: {
target: [ 'a' ],
href: [ 'a' ],
},
} );
// @ts-ignore
div.setHTML( option.description, { sanitizer } );
return ;
}
return stripHTML( option.description );
}
function stripHTML( html ) {
const doc = new DOMParser().parseFromString( html, 'text/html' );
return doc.body.textContent || '';
}
*/