Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/list-reusable-blocks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"@wordpress/blob": "file:../blob",
"@wordpress/components": "file:../components",
"@wordpress/compose": "file:../compose",
"@wordpress/core-data": "file:../core-data",
"@wordpress/element": "file:../element",
"@wordpress/i18n": "file:../i18n",
"change-case": "^4.1.2"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
/**
* WordPress dependencies
*/
import { Button, Dropdown } from '@wordpress/components';
import { pipe } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
import { Dropdown, Button } from '@wordpress/components';

/**
* Internal dependencies
*/
import type { ImportDropdownProps } from '../../utils/types';
import ImportForm from '../import-form';

function ImportDropdown( { onUpload } ) {
function ImportDropdown( { onUpload }: ImportDropdownProps ) {
return (
<Dropdown
popoverProps={ { placement: 'bottom-start' } }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,51 @@
/**
* WordPress dependencies
*/
import { useState, useRef } from '@wordpress/element';
import { Button, Notice } from '@wordpress/components';
import { withInstanceId } from '@wordpress/compose';
import { useRef, useState } from '@wordpress/element';
import { __, _x } from '@wordpress/i18n';
import { Button, Notice } from '@wordpress/components';

/**
* Internal dependencies
*/
import importReusableBlock from '../../utils/import';
import type { ImportFormProps, ReusableBlock } from '../../utils/types';

function ImportForm( { instanceId, onUpload } ) {
function ImportForm( { instanceId, onUpload }: ImportFormProps ) {
const inputId = 'list-reusable-blocks-import-form-' + instanceId;

const formRef = useRef();
const formRef = useRef< HTMLFormElement >( null );
const [ isLoading, setIsLoading ] = useState( false );
const [ error, setError ] = useState( null );
const [ file, setFile ] = useState( null );
const [ error, setError ] = useState< string | null >( null );
const [ file, setFile ] = useState< File | null >( null );

const onChangeFile = ( event ) => {
setFile( event.target.files[ 0 ] );
const onChangeFile = ( event: React.ChangeEvent< HTMLInputElement > ) => {
setFile( event.target.files?.[ 0 ] || null );
setError( null );
};

const onSubmit = ( event ) => {
const onSubmit = ( event: React.FormEvent< HTMLFormElement > ) => {
event.preventDefault();
if ( ! file ) {
return;
}
setIsLoading( true );
importReusableBlock( file )
.then( ( reusableBlock ) => {
if ( ! formRef ) {
.then( ( reusableBlock: ReusableBlock ) => {
if ( ! formRef.current ) {
return;
}

setIsLoading( false );
onUpload( reusableBlock );
} )
.catch( ( errors ) => {
if ( ! formRef ) {
.catch( ( errors: Error ) => {
if ( ! formRef.current ) {
return;
}

let uiMessage;
let uiMessage: string;
switch ( errors.message ) {
case 'Invalid JSON file':
uiMessage = __( 'Invalid JSON file' );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,23 @@ import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
import exportReusableBlock from './utils/export';
import ImportDropdown from './components/import-dropdown';
import exportReusableBlock from './utils/export';

// Setup Export Links.
document.body.addEventListener( 'click', ( event ) => {
if (
! event.target.classList.contains( 'wp-list-reusable-blocks__export' )
) {
document.body.addEventListener( 'click', ( event: MouseEvent ) => {
const target = event.target as HTMLElement;

if ( ! target.classList.contains( 'wp-list-reusable-blocks__export' ) ) {
return;
}

event.preventDefault();
exportReusableBlock( event.target.dataset.id );

const blockId = target.dataset.id;
if ( blockId ) {
exportReusableBlock( Number( blockId ) );
}
} );

// Setup Import Form.
Expand All @@ -34,14 +39,19 @@ document.addEventListener( 'DOMContentLoaded', () => {
notice.innerHTML = `<p>${ __( 'Pattern imported successfully!' ) }</p>`;

const headerEnd = document.querySelector( '.wp-header-end' );
if ( ! headerEnd ) {
if ( ! headerEnd || ! headerEnd.parentNode ) {
return;
}
headerEnd.parentNode.insertBefore( notice, headerEnd );
};

const container = document.createElement( 'div' );
container.className = 'list-reusable-blocks__container';

if ( ! button.parentNode ) {
return;
}

button.parentNode.insertBefore( container, button );
createRoot( container ).render(
<StrictMode>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,26 @@ import apiFetch from '@wordpress/api-fetch';
* Internal dependencies
*/
import { downloadBlob } from '@wordpress/blob';
import type { Post, PostType } from './types';

/**
* Export a reusable block as a JSON file.
*
* @param {number} id
* @param id - The ID of the reusable block to export
*/
async function exportReusableBlock( id ) {
const postType = await apiFetch( { path: `/wp/v2/types/wp_block` } );
const post = await apiFetch( {
async function exportReusableBlock( id: number ): Promise< void > {
const postType = await apiFetch< PostType >( {
path: `/wp/v2/types/wp_block`,
} );

const post = await apiFetch< Post >( {
path: `/wp/v2/${ postType.rest_base }/${ id }?context=edit`,
} );

const title = post.title.raw;
const content = post.content.raw;
const syncStatus = post.wp_pattern_sync_status;

const fileContent = JSON.stringify(
{
__file: 'wp_block',
Expand All @@ -36,6 +42,7 @@ async function exportReusableBlock( id ) {
null,
2
);

const fileName = kebabCase( title ) + '.json';

downloadBlob( fileName, fileContent, 'application/json' );
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/**
* Reads the textual content of the given file.
*
* @param {File} file File.
* @return {Promise<string>} Content of the file.
* @param file - File to read
* @return Promise that resolves to the content of the file
*/
export function readTextFile( file ) {
export function readTextFile( file: File ): Promise< string > {
const reader = new window.FileReader();
return new Promise( ( resolve ) => {
reader.onload = () => {
resolve( reader.result );
resolve( reader.result as string );
};
reader.readAsText( file );
} );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,29 @@ import apiFetch from '@wordpress/api-fetch';
* Internal dependencies
*/
import { readTextFile } from './file';
import type {
ParsedContent,
PostType,
ReusableBlock,
ReusableBlockData,
} from './types';

/**
* Import a reusable block from a JSON file.
*
* @param {File} file File.
* @return {Promise} Promise returning the imported reusable block.
* @param file - File to import
* @return Promise returning the imported reusable block
*/
async function importReusableBlock( file ) {
async function importReusableBlock( file: File ): Promise< ReusableBlock > {
const fileContent = await readTextFile( file );
let parsedContent;
let parsedContent: ParsedContent;

try {
parsedContent = JSON.parse( fileContent );
} catch {
throw new Error( 'Invalid JSON file' );
}

if (
parsedContent.__file !== 'wp_block' ||
! parsedContent.title ||
Expand All @@ -33,8 +41,12 @@ async function importReusableBlock( file ) {
) {
throw new Error( 'Invalid pattern JSON file' );
}
const postType = await apiFetch( { path: `/wp/v2/types/wp_block` } );
const reusableBlock = await apiFetch( {

const postType = await apiFetch< PostType >( {
path: `/wp/v2/types/wp_block`,
} );

const reusableBlock = await apiFetch< ReusableBlock >( {
path: `/wp/v2/${ postType.rest_base }`,
data: {
title: parsedContent.title,
Expand All @@ -44,7 +56,7 @@ async function importReusableBlock( file ) {
parsedContent.syncStatus === 'unsynced'
? { wp_pattern_sync_status: parsedContent.syncStatus }
: undefined,
},
} satisfies ReusableBlockData,
method: 'POST',
} );

Expand Down
57 changes: 57 additions & 0 deletions packages/list-reusable-blocks/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* WordPress dependencies
*/
import type { Post as CorePost, Type } from '@wordpress/core-data';

export interface ImportDropdownProps {
onUpload: ( data: ReusableBlock ) => void;
}

export interface ImportFormProps {
instanceId: string | number;
onUpload: ( reusableBlock: ReusableBlock ) => void;
}

export type PostType = Type;

export type Post = CorePost & { wp_pattern_sync_status?: string };

export interface ExportedBlock {
__file: string;
title: string;
content: string;
syncStatus: string;
}

export interface ParsedContent {
__file: string;
title: string;
content: string;
syncStatus?: string;
[ key: string ]: unknown;
Comment thread
Infinite-Null marked this conversation as resolved.
Resolved
}

export interface ReusableBlockMeta {
wp_pattern_sync_status?: string;
}

export interface ReusableBlockData {
title: string;
content: string;
status: string;
meta?: ReusableBlockMeta;
}

export interface ReusableBlock {
id: number;
title: {
raw: string;
rendered: string;
};
content: {
raw: string;
rendered: string;
};
status: string;
[ key: string ]: unknown;
}
13 changes: 13 additions & 0 deletions packages/list-reusable-blocks/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "https://json.schemastore.org/tsconfig.json",
"extends": "../../tsconfig.base.json",
"references": [
{ "path": "../api-fetch" },
{ "path": "../blob" },
{ "path": "../compose" },
{ "path": "../components" },
{ "path": "../core-data" },
{ "path": "../element" },
{ "path": "../i18n" }
]
}
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
{ "path": "packages/latex-to-mathml" },
{ "path": "packages/lazy-editor" },
{ "path": "packages/lazy-import" },
{ "path": "packages/list-reusable-blocks" },
{ "path": "packages/media-editor" },
{ "path": "packages/media-fields" },
{ "path": "packages/media-utils" },
Expand Down
Loading