Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
23 changes: 23 additions & 0 deletions lib/experimental/content-guidelines/load.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php
/**
* Bootstraps the Content Guidelines page in wp-admin under Settings.
*
* @package gutenberg
*/

add_action( 'admin_menu', 'gutenberg_register_content_guidelines_settings_submenu', 10 );

/**
* Registers the Content Guidelines submenu item under Settings.
* Uses the same layout/style as the Font Library admin page (wp-admin integrated).
*/
function gutenberg_register_content_guidelines_settings_submenu() {
add_submenu_page(
'options-general.php',
__( 'Content Guidelines', 'gutenberg' ),
__( 'Content Guidelines', 'gutenberg' ),
'manage_options',
'content-guidelines-wp-admin',
'gutenberg_content_guidelines_wp_admin_render_page'
);
}
2 changes: 2 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,5 @@ function gutenberg_is_experiment_enabled( $name ) {
if ( gutenberg_is_experiment_enabled( 'gutenberg-content-guidelines' ) ) {
require __DIR__ . '/experimental/content-guidelines/index.php';
}

require __DIR__ . '/experimental/content-guidelines/load.php';
23 changes: 23 additions & 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 package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
]
},
"font-library",
"content-guidelines",
"connectors"
]
},
Expand Down
209 changes: 209 additions & 0 deletions routes/content-guidelines/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';
import { dispatch, select } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
import { store as noticesStore } from '@wordpress/notices';

/**
* Internal dependencies
*/
import { STORE_NAME } from './store';
import type { BlockGuideline, GuidelineCategories, Revision } from './types';

interface RestGuidelinesResponse {
id: number;
status: string;
guideline_categories?: Record< string, { guidelines?: string } > & {
blocks?: BlockGuideline[];
};
}

export async function fetchContentGuidelines(): Promise< RestGuidelinesResponse > {
const { setFromResponse } = dispatch( STORE_NAME ) as {
setFromResponse: ( response: RestGuidelinesResponse ) => void;
};

try {
const response = ( await apiFetch( {
path: '/wp/v2/content-guidelines?context=edit',
} ) ) as RestGuidelinesResponse;

setFromResponse( response );

return response;
} catch ( error: unknown ) {
const { createErrorNotice } = dispatch( noticesStore ) as {
createErrorNotice: (
message: string,
options?: { type?: 'snackbar' | 'default'; context?: string }
) => void;
};

createErrorNotice(
__(
'There was an error loading your content guidelines. Please try again.'
),
{ type: 'snackbar' }
);
throw error;
}
}

// Categories fork function extended: adds `blocks` to the payload.
export async function saveContentGuidelines(): Promise< RestGuidelinesResponse > {
// @ts-ignore
const { setFromResponse } = dispatch( STORE_NAME );

const guidelinesStore = select( STORE_NAME ) as {
getId: () => number | null;
getStatus: () => string | null;
getAllGuidelines: () => Partial< Record< string, string > >;
getGuideline: ( category: string ) => string;
getBlocks: () => BlockGuideline[];
};

const id = guidelinesStore.getId();
const status = guidelinesStore.getStatus() || 'draft';
const categories = guidelinesStore.getAllGuidelines();
const blocks = guidelinesStore.getBlocks();

const data = {
id: id && id > 0 ? id : undefined,
status,
guideline_categories: {
site: {
guidelines: categories.site,
},
copy: {
guidelines: categories.copy,
},
images: {
guidelines: categories.images,
},
additional: {
guidelines: categories.additional,
},
blocks,
},
};

const { createSuccessNotice, createErrorNotice } = dispatch( noticesStore );

try {
const hasExistingId = !! ( id && id > 0 );
const path = hasExistingId
? `/wp/v2/content-guidelines/${ id }`
: '/wp/v2/content-guidelines';
const method = hasExistingId ? 'PUT' : 'POST';

const response = ( await apiFetch( {
path,
method,
data,
} ) ) as RestGuidelinesResponse;

setFromResponse( response );

createSuccessNotice( __( 'Content guidelines saved.' ), {
type: 'snackbar',
} );

return response;
} catch ( error: unknown ) {
createErrorNotice(
__(
'There was an error saving your content guidelines. Please try again.'
),
{ type: 'snackbar' }
);
throw error;
}
}

// Actions fork addition: lazy-loaded when user opens the revisions screen.
export async function fetchRevisions( postId: number ): Promise< void > {
const { setRevisions } = dispatch( STORE_NAME ) as {
setRevisions: (
revisions: Array< Revision & { categories: GuidelineCategories } >
) => void;
};

try {
const response = ( await apiFetch( {
path: `/wp/v2/content-guidelines/${ postId }/revisions`,
} ) ) as Array<
Revision & {
guideline_categories?: Record<
string,
{ guidelines?: string }
> & { blocks?: BlockGuideline[] };
}
>;

const revisions = response.map( ( revision ) => {
const raw = revision.guideline_categories ?? {};
const categories: GuidelineCategories = {
site: { guidelines: raw.site?.guidelines ?? '' },
copy: { guidelines: raw.copy?.guidelines ?? '' },
images: { guidelines: raw.images?.guidelines ?? '' },
additional: { guidelines: raw.additional?.guidelines ?? '' },
blocks: Array.isArray( raw.blocks ) ? raw.blocks : [],
};
return {
id: revision.id,
date: revision.date,
author_name: revision.author_name,
categories,
};
} );

setRevisions( revisions );
} catch ( error: unknown ) {
const { createErrorNotice } = dispatch( noticesStore ) as {
createErrorNotice: (
message: string,
options?: { type?: 'snackbar' | 'default' }
) => void;
};

createErrorNotice(
__(
'There was an error loading revision history. Please try again.'
),
{ type: 'snackbar' }
);
throw error;
}
}

// Restore a revision
export async function restoreRevision(
revisionId: number,
categories: GuidelineCategories
): Promise< void > {
const { setGuideline, setBlocks, setCurrentRevisionId } = dispatch(
STORE_NAME
) as {
setGuideline: ( category: string, value: string ) => void;
setBlocks: ( blocks: BlockGuideline[] ) => void;
setCurrentRevisionId: ( id: number ) => void;
};

// Write text categories to the store.
( [ 'site', 'copy', 'images', 'additional' ] as const ).forEach(
( slug ) => {
setGuideline( slug, categories[ slug ].guidelines );
}
);

// Write blocks to the store.
setBlocks( categories.blocks );

// Persist to backend (reuses save logic including success/error notices).
await saveContentGuidelines();

// Mark this revision as current.
setCurrentRevisionId( revisionId );
}
Loading
Loading