Improve SEO settings table and actions

This commit is contained in:
Tetrakern 2023-09-08 00:24:45 +02:00
parent 71d9372757
commit 14ff3a45c6
10 changed files with 460 additions and 438 deletions

View File

@ -221,7 +221,7 @@ Fictioneer customizes WordPress by using as many standard action and filter hook
| `admin_init` | `fictioneer_register_settings`, `fictioneer_skip_dashboard`, `fictioneer_initialize_roles`, `fictioneer_bring_out_legacy_trash`
| `admin_menu` | `fictioneer_add_admin_menu`, `fictioneer_remove_dashboard_menu`, `fictioneer_remove_comments_menu_page`, `fictioneer_remove_sub_menus`
| `admin_notices` | `fictioneer_admin_profile_notices`, `fictioneer_admin_settings_notices`, `fictioneer_admin_update_notice`
| `admin_post_*` | `fictioneer_delete_all_epubs`, `admin_post_purge_all_seo_schemas`, `fictioneer_purge_seo_meta_caches`, `fictioneer_tools_add_moderator_role`, `fictioneer_tools_move_story_tags_to_genres`, `fictioneer_tools_duplicate_story_tags_to_genres`, `fictioneer_tools_purge_story_data_caches`, `fictioneer_tools_move_chapter_tags_to_genres`, `fictioneer_tools_duplicate_chapter_tags_to_genres`, `fictioneer_tools_append_default_genres`, `fictioneer_tools_append_default_tags`, `fictioneer_tools_remove_unused_tags`, `fictioneer_tools_reset_post_relationship_registry`, `fictioneer_tools_fix_users`, `fictioneer_tools_fix_stories`, `fictioneer_tools_fix_chapters`, `fictioneer_tools_fix_collections`, `fictioneer_tools_fix_pages`, `fictioneer_tools_fix_posts`, `fictioneer_tools_fix_recommendations`, `fictioneer_admin_profile_unset_oauth`, `fictioneer_admin_profile_clear_data_node`, `fictioneer_update_frontend_profile`, `fictioneer_cancel_frontend_email_change`, `fictioneer_add_role`, `fictioneer_remove_role`, `fictioneer_rename_role`
| `admin_post_*` | `fictioneer_delete_all_epubs`, `admin_post_purge_all_seo_schemas`, `fictioneer_tools_add_moderator_role`, `fictioneer_tools_move_story_tags_to_genres`, `fictioneer_tools_duplicate_story_tags_to_genres`, `fictioneer_tools_purge_story_data_caches`, `fictioneer_tools_move_chapter_tags_to_genres`, `fictioneer_tools_duplicate_chapter_tags_to_genres`, `fictioneer_tools_append_default_genres`, `fictioneer_tools_append_default_tags`, `fictioneer_tools_remove_unused_tags`, `fictioneer_tools_reset_post_relationship_registry`, `fictioneer_tools_fix_users`, `fictioneer_tools_fix_stories`, `fictioneer_tools_fix_chapters`, `fictioneer_tools_fix_collections`, `fictioneer_tools_fix_pages`, `fictioneer_tools_fix_posts`, `fictioneer_tools_fix_recommendations`, `fictioneer_admin_profile_unset_oauth`, `fictioneer_admin_profile_clear_data_node`, `fictioneer_update_frontend_profile`, `fictioneer_cancel_frontend_email_change`, `fictioneer_add_role`, `fictioneer_remove_role`, `fictioneer_rename_role`
| `after_setup_theme` | `fictioneer_theme_setup`
| `comment_post` | `fictioneer_comment_post`, `fictioneer_post_comment_to_discord`
| `current_screen` | `fictioneer_restrict_admin_only_pages`, `fictioneer_restrict_comment_edit`
@ -246,7 +246,7 @@ Fictioneer customizes WordPress by using as many standard action and filter hook
| `template_redirect` | `fictioneer_disable_date_archives`, `fictioneer_generate_epub`, `fictioneer_handle_oauth`, `fictioneer_logout`, `fictioneer_disable_attachment_pages`, `fictioneer_gate_unpublished_content`
| `trashed_post` | `fictioneer_refresh_post_caches`, `fictioneer_track_chapter_and_story_updates`, `fictioneer_update_modified_date_on_story_for_chapter`, `fictioneer_purge_transients`
| `untrash_post` | `fictioneer_refresh_post_caches`, `fictioneer_track_chapter_and_story_updates`, `fictioneer_update_modified_date_on_story_for_chapter`, `fictioneer_purge_transients`
| `wp_ajax_*` | `fictioneer_ajax_clear_my_checkmarks`, `fictioneer_ajax_clear_my_comments`, `fictioneer_ajax_clear_my_comment_subscriptions`, `fictioneer_ajax_clear_my_follows`, `fictioneer_ajax_clear_my_reminders`, `fictioneer_ajax_delete_epub`, `fictioneer_ajax_delete_my_account`, `fictioneer_ajax_delete_my_comment`, `fictioneer_ajax_edit_comment`, `fictioneer_ajax_get_avatar`, `fictioneer_ajax_get_comment_form`, `fictioneer_ajax_get_comment_section`, `fictioneer_ajax_get_finished_checkmarks_list`, `fictioneer_ajax_get_follows_list`, `fictioneer_ajax_get_follows_notifications`, `fictioneer_ajax_get_reminders_list`, `fictioneer_ajax_mark_follows_read`, `fictioneer_ajax_moderate_comment`, `fictioneer_ajax_purge_schema`, `fictioneer_ajax_report_comment`, `fictioneer_ajax_save_bookmarks`, `fictioneer_ajax_set_checkmark`, `fictioneer_ajax_submit_comment`, `fictioneer_ajax_toggle_follow`, `fictioneer_ajax_toggle_reminder`, `fictioneer_ajax_unset_my_oauth`, `fictioneer_ajax_get_user_data`, `fictioneer_ajax_get_auth`
| `wp_ajax_*` | `fictioneer_ajax_clear_my_checkmarks`, `fictioneer_ajax_clear_my_comments`, `fictioneer_ajax_clear_my_comment_subscriptions`, `fictioneer_ajax_clear_my_follows`, `fictioneer_ajax_clear_my_reminders`, `fictioneer_ajax_delete_epub`, `fictioneer_ajax_delete_my_account`, `fictioneer_ajax_delete_my_comment`, `fictioneer_ajax_edit_comment`, `fictioneer_ajax_get_avatar`, `fictioneer_ajax_get_comment_form`, `fictioneer_ajax_get_comment_section`, `fictioneer_ajax_get_finished_checkmarks_list`, `fictioneer_ajax_get_follows_list`, `fictioneer_ajax_get_follows_notifications`, `fictioneer_ajax_get_reminders_list`, `fictioneer_ajax_mark_follows_read`, `fictioneer_ajax_moderate_comment`, `fictioneer_ajax_report_comment`, `fictioneer_ajax_save_bookmarks`, `fictioneer_ajax_set_checkmark`, `fictioneer_ajax_submit_comment`, `fictioneer_ajax_toggle_follow`, `fictioneer_ajax_toggle_reminder`, `fictioneer_ajax_unset_my_oauth`, `fictioneer_ajax_get_user_data`, `fictioneer_ajax_get_auth`, `fictioneer_ajax_purge_schema`, `fictioneer_ajax_purge_all_schemas`
| `wp_ajax_nopriv_*` | `fictioneer_ajax_get_comment_form`, `fictioneer_ajax_get_comment_section`, `fictioneer_ajax_submit_comment`, `fictioneer_ajax_get_auth`
| `wp_before_admin_bar_render` | `fictioneer_remove_admin_bar_links`, `fictioneer_remove_dashboard_from_admin_bar`
| `wp_dashboard_setup` | `fictioneer_remove_dashboard_widgets`

File diff suppressed because one or more lines are too long

View File

@ -72,6 +72,7 @@ function fictioneer_admin_scripts( $hook_suffix ) {
'fictioneer_ajax',
array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'rest_url' => get_rest_url( null, 'fictioneer/v1/' ),
'fictioneer_nonce' => wp_create_nonce( 'fictioneer_nonce' )
)
);

View File

@ -88,8 +88,6 @@ if ( ! defined( 'FICTIONEER_ADMIN_SETTINGS_NOTICES' ) ) {
'fictioneer-append-default-tags' => __( 'Default tags have been added to the list.', 'fictioneer' ),
'fictioneer-remove-unused-tags' => __( 'All unused tags have been removed.', 'fictioneer' ),
'fictioneer-deleted-epubs' => __( 'All ePUB files have been deleted.', 'fictioneer' ),
'fictioneer-purged-seo-schemas' => __( 'All SEO schemas have been purged.', 'fictioneer' ),
'fictioneer-purged-seo-meta-caches' => __( 'All SEO meta caches have been purged.', 'fictioneer' ),
'fictioneer-purge-theme-caches' => __( 'Theme caches have been purged.', 'fictioneer' ),
'fictioneer-added-moderator-role' => __( 'Moderator role has been added.', 'fictioneer' ),
'fictioneer-not-added-moderator-role' => __( 'Moderator role could not be added or already exists.', 'fictioneer' ),
@ -180,102 +178,6 @@ function fictioneer_delete_all_epubs() {
}
add_action( 'admin_post_fictioneer_delete_all_epubs', 'fictioneer_delete_all_epubs' );
// =============================================================================
// SEO ACTIONS
// =============================================================================
/**
* Purge all SEO schemas
*
* This function deletes the "fictioneer_schema" meta data from all posts and pages
* of specified post types.
*
* @since Fictioneer 5.2.5
*/
function fictioneer_purge_all_seo_schemas() {
// Verify request
fictioneer_verify_tool_action( 'fictioneer_purge_all_seo_schemas' );
// Setup
$all_ids = get_posts(
array(
'post_type' => ['page', 'post', 'fcn_story', 'fcn_chapter', 'fcn_recommendation', 'fcn_collection'],
'numberposts' => -1,
'fields' => 'ids',
'update_post_meta_cache' => false,
'update_post_term_cache' => false
)
);
// If IDs were found...
if ( ! empty( $all_ids ) ) {
// Loop all IDs to delete schemas
foreach ( $all_ids as $id ) {
delete_post_meta( $id, 'fictioneer_schema' );
}
// Log
fictioneer_log(
__( 'Purged all schema graphs.', 'fictioneer' )
);
// Purge caches
fictioneer_purge_all_caches();
}
// Finish
fictioneer_finish_tool_action( 'fictioneer-purged-seo-schemas' );
}
add_action( 'admin_post_fictioneer_purge_all_seo_schemas', 'fictioneer_purge_all_seo_schemas' );
/**
* Purge all SEO meta caches
*
* This function deletes the cached SEO meta values for all posts and custom post types
* specified in the post_type parameter.
*
* @since Fictioneer 5.2.5
*/
function fictioneer_purge_seo_meta_caches() {
// Verify request
fictioneer_verify_tool_action( 'fictioneer_purge_seo_meta_caches' );
// Setup
$all_ids = get_posts(
array(
'post_type' => ['page', 'post', 'fcn_story', 'fcn_chapter', 'fcn_recommendation', 'fcn_collection'],
'numberposts' => -1,
'fields' => 'ids',
'update_post_meta_cache' => false,
'update_post_term_cache' => false
)
);
// If IDs were found...
if ( $all_ids ) {
// Loop all IDs to delete SEO meta caches
foreach ( $all_ids as $id ) {
delete_post_meta( $id, 'fictioneer_seo_title_cache' );
delete_post_meta( $id, 'fictioneer_seo_description_cache' );
delete_post_meta( $id, 'fictioneer_seo_og_image_cache' );
}
// Log
fictioneer_log(
__( 'Purged all SEO meta caches.', 'fictioneer' )
);
// Purge caches
fictioneer_purge_all_caches();
}
// Finish
fictioneer_finish_tool_action( 'fictioneer-purged-seo-meta-caches' );
}
add_action( 'admin_post_fictioneer_purge_seo_meta_caches', 'fictioneer_purge_seo_meta_caches' );
// =============================================================================
// ROLE TOOLS ACTIONS
// =============================================================================

View File

@ -101,15 +101,15 @@ add_action( 'wp_ajax_fictioneer_ajax_delete_epub', 'fictioneer_ajax_delete_epub'
function fictioneer_ajax_purge_schema() {
if ( fictioneer_validate_settings_ajax() ) {
// Abort if...
if ( empty( $_POST['id'] ?? '' ) ) {
if ( empty( $_POST['post_id'] ?? '' ) ) {
wp_send_json_error( array( 'notice' => __( 'ID missing', 'fictioneer' ) ) );
}
// Setup
$id = fictioneer_validate_id( $_POST['id'] );
$post_id = fictioneer_validate_id( $_POST['post_id'] );
// Delete schema stored in post meta
if ( $id && delete_post_meta( $id, 'fictioneer_schema' ) ) {
if ( $post_id && delete_post_meta( $post_id, 'fictioneer_schema' ) ) {
// Log
fictioneer_log(
sprintf(
@ -118,19 +118,22 @@ function fictioneer_ajax_purge_schema() {
'Pattern for purged schema graphs in logs: "Purged schema graph of #{%s}".',
'fictioneer'
),
$id
$post_id
)
);
// Purge caches
fictioneer_refresh_post_caches( $id );
delete_post_meta( $post_id, 'fictioneer_seo_title_cache' );
delete_post_meta( $post_id, 'fictioneer_seo_description_cache' );
delete_post_meta( $post_id, 'fictioneer_seo_og_image_cache' );
fictioneer_refresh_post_caches( $post_id );
// Success
wp_send_json_success(
array(
'notice' => sprintf(
__( 'Schema of post #%s purged.', 'fictioneer' ),
$id
$post_id
)
)
);
@ -140,7 +143,7 @@ function fictioneer_ajax_purge_schema() {
array(
'notice' => sprintf(
__( 'Error. Schema of post #%s could not be purged.', 'fictioneer' ),
$id
$post_id
)
)
);
@ -152,4 +155,88 @@ function fictioneer_ajax_purge_schema() {
}
add_action( 'wp_ajax_fictioneer_ajax_purge_schema', 'fictioneer_ajax_purge_schema' );
/**
* AJAX: Purge schemas for all posts and pages
*
* @since Fictioneer 5.7.2
*/
function fictioneer_ajax_purge_all_schemas() {
// Validate
if ( ! fictioneer_validate_settings_ajax() ) {
wp_send_json_error( array( 'notice' => __( 'Invalid request.', 'fictioneer' ) ) );
}
// Setup
$offset = absint( $_POST['offset'] ?? 0 );
$limit = 100;
// Query
$args = array(
'post_type' => ['post', 'page', 'fcn_story', 'fcn_chapter', 'fcn_collection', 'fcn_recommendation'],
'post_status' => 'any',
'fields' => 'ids',
'ignore_sticky_posts' => 1,
'offset' => $offset,
'posts_per_page' => $limit,
'update_post_meta_cache' => false,
'update_post_term_cache' => false
);
$query = new WP_Query( $args );
// If post have been found...
if ( $query->have_posts() ) {
global $wpdb;
// Prepare SQL
$placeholders = implode( ', ', array_fill( 0, count( $query->posts ), '%d' ) );
$meta_keys = array(
'fictioneer_schema',
'fictioneer_seo_title_cache',
'fictioneer_seo_description_cache',
'fictioneer_seo_og_image_cache'
);
// Execute
foreach ( $meta_keys as $meta_key ) {
$wpdb->query(
$wpdb->prepare(
"DELETE FROM {$wpdb->postmeta} WHERE post_id IN ($placeholders) AND meta_key = %s",
array_merge( $query->posts, [ $meta_key ] )
)
);
}
// Log
fictioneer_log(
__( 'Purged all schemas graphs.', 'fictioneer' )
);
// ... continue with next batch
wp_send_json_success(
array(
'finished' => false,
'processed' => count( $query->posts ),
'total' => $query->found_posts,
'next_offset' => $offset + $limit
)
);
} else {
// Purge caches
fictioneer_purge_all_caches();
// ... all done
wp_send_json_success(
array(
'finished' => true,
'processed' => 0,
'total' => $query->found_posts,
'next_offset' => -1
)
);
}
}
add_action( 'wp_ajax_fictioneer_ajax_purge_all_schemas', 'fictioneer_ajax_purge_all_schemas' );
?>

View File

@ -10,7 +10,7 @@
<?php
class Fictioneer_Epubs_List extends WP_List_Table {
class Fictioneer_Epubs_Table extends WP_List_Table {
private $epubs = [];
private $current_epubs = [];
@ -155,14 +155,6 @@ class Fictioneer_Epubs_List extends WP_List_Table {
}
}
/**
* Render extra content in the table navigation section
*
* @since Fictioneer 5.7.2
*
* @param string $which Either "top" or "bottom".
*/
protected function extra_tablenav( $which ) {
// Setup
$actions = [];
@ -259,7 +251,7 @@ class Fictioneer_Epubs_List extends WP_List_Table {
}
}
$epubs_table = new Fictioneer_Epubs_List();
$epubs_table = new Fictioneer_Epubs_Table();
$epubs_table->prepare_items();
?>

View File

@ -10,27 +10,172 @@
<?php
// Setup
$per_page = 50;
$page_number = absint( $_GET['paged'] ?? 1 );
$purge_all_url = wp_nonce_url( admin_url( 'admin-post.php?action=fictioneer_purge_all_seo_schemas' ), 'fictioneer_purge_all_seo_schemas', 'fictioneer_nonce' );
$purge_meta_url = wp_nonce_url( admin_url( 'admin-post.php?action=fictioneer_purge_seo_meta_caches' ), 'fictioneer_purge_seo_meta_caches', 'fictioneer_nonce' );
class Fictioneer_Seo_Table extends WP_List_Table {
public $page = 1;
public $per_page = 25;
// Query
$query_args = array(
'post_type' => ['page', 'post', 'fcn_story', 'fcn_chapter', 'fcn_recommendation', 'fcn_collection'],
'post_status' => 'publish',
'orderby' => 'modified',
'order' => 'DESC',
'paged' => $page_number,
'posts_per_page' => $per_page,
'fields' => 'ids',
'update_post_meta_cache' => false,
'update_post_term_cache' => false
);
public function __construct() {
parent::__construct([
'singular' => 'epub',
'plural' => 'epubs',
'ajax' => false
]);
}
$query = new WP_Query( $query_args );
$post_ids = $query->posts;
public function prepare_items() {
// Setup
$columns = $this->get_columns();
$hidden = [];
$sortable = $this->get_sortable_columns();
$primary = 'title';
$this->page = absint( $_GET['paged'] ?? 1 );
// Sort
$orderby = array_intersect( [sanitize_key( $_GET['orderby'] ?? 0 )], ['title', 'type', 'modified'] );
$orderby = reset( $orderby ) ?: 'modified';
$order = array_intersect( [sanitize_key( $_GET['order'] ?? 0 )], ['desc', 'asc'] );
$order = reset( $order ) ?: 'desc';
// Query
$query_args = array(
'post_type' => ['page', 'post', 'fcn_story', 'fcn_chapter', 'fcn_recommendation', 'fcn_collection'],
'post_status' => 'publish',
'orderby' => $orderby,
'order' => $order,
'paged' => $this->page,
'posts_per_page' => $this->per_page,
'update_post_meta_cache' => false,
'update_post_term_cache' => false
);
$query = new WP_Query( $query_args );
// Pagination
$this->set_pagination_args(
array(
'total_items' => $query->found_posts,
'per_page' => $this->per_page,
'total_pages' => ceil( $query->found_posts / $this->per_page )
)
);
// Data
$this->_column_headers = [ $columns, $hidden, $sortable, $primary ];
$this->items = $query->posts;
}
public function get_columns() {
return array(
'title' => __( 'Title', 'fictioneer' ),
'description' => __( 'Description', 'fictioneer' ),
'type' => __( 'Type', 'fictioneer' ),
'date' => __( 'Modified', 'fictioneer' )
);
}
public function column_default( $item, $column_name ) {
$image = get_post_meta( $item->ID, 'fictioneer_seo_og_image_cache', true );
$image_url = $image ? $image['url'] : get_template_directory_uri() . '/img/no_image_placeholder.svg';
$schema = get_post_meta( $item->ID, 'fictioneer_schema', true );
$schema_text = stripslashes( json_encode( json_decode( $schema ), JSON_PRETTY_PRINT ) );
$seo_description = fictioneer_get_seo_description( $item->ID );
$link = get_the_permalink( $item->ID );
$title = mb_strimwidth( fictioneer_get_seo_title( $item->ID ), 0, 48, '…' );
$template = get_page_template_slug( $item->ID );
$is_excluded = in_array( $template, ['singular-bookshelf.php', 'singular-bookmarks.php', 'user-profile.php'] );
$actions = array(
'edit' => '<a href="' . get_edit_post_link( $item->ID ) . '">' . __( 'Edit', 'fictioneer' ) . '</a>'
);
if ( $schema ) {
$actions['schema'] = '<a data-dialog-target="schema-dialog">' . __( 'Schema', 'fcnl' ) . '</a>';
$actions['trash'] = "<a href='#' data-purge-schema data-id='{$item->ID}'>" . __( 'Purge', 'fictioneer' ) . '</a>';
}
switch ( $column_name ) {
case 'title':
printf(
_x( '%s<span>%s%s</span> %s<div style="clear: both;"></div>', 'SEO table row item title column.', 'fictioneer' ),
'<img src="' . $image_url . '" width="31" height="46" class="row-thumbnail">',
"<a href='{$link}' class='row-title'>{$title}</a></span>",
$is_excluded ? _x( ' — Excluded', 'SEO table row item title column excluded note.', 'fictioneer' ) : '',
$this->row_actions( $actions )
);
break;
case 'description':
echo $seo_description;
if ( $schema ) {
echo "<div data-schema-id='{$item->ID}' hidden>{$schema_text}</div>";
}
break;
case 'type':
echo get_post_type_object( $item->post_type )->labels->singular_name;
break;
case 'date':
printf(
_x( '%1$s<br>at %2$s', 'Table date and time column.', 'fictioneer' ),
get_the_modified_date( get_option( 'date_format' ), $item->ID ),
get_the_modified_date( get_option( 'time_format' ), $item->ID )
);
break;
}
}
public function single_row( $item ) {
// Default
static $row_class = '';
$row_class = $row_class == '' ? 'alternate' : '';
// Setup
$schema = get_post_meta( $item->ID, 'fictioneer_schema', true );
// Add class or not
if ( ! $schema ) {
$row_class .= ' no-schema';
}
// Output
echo "<tr class='{$row_class}'>";
$this->single_row_columns( $item );
echo '</tr>';
}
protected function extra_tablenav( $which ) {
// Setup
$actions = [];
// Purge All
if ( current_user_can( 'manage_options' ) ) {
$actions[] = sprintf(
'<a href="#" class="button" data-action-purge-all-schemas data-disable-with="%s">%s</a>',
esc_html( __( 'Purging…', 'fictioneer' ) ),
__( 'Purge All', 'fictioneer' )
);
}
// Output
if ( ! empty( $actions ) ) {
// Start HTML ---> ?>
<div class="alignleft actions"><?php echo implode( ' ', $actions ); ?></div>
<?php // <--- End HTML
}
}
protected function get_sortable_columns() {
return array(
'title' => ['title', false],
'type' => ['type', false],
'date' => ['modified', false]
);
}
}
$seo_table = new Fictioneer_Seo_Table();
$seo_table->prepare_items();
?>
@ -44,140 +189,41 @@ $post_ids = $query->posts;
<div class="fictioneer-card">
<div class="fictioneer-card__wrapper">
<h3 class="fictioneer-card__header"><?php _e( 'SEO Data', 'fictioneer' ); ?></h3>
<div class="fictioneer-card__content">
<div class="fictioneer-card__row">
<p><?php
printf(
__( 'This is the generated <a href="%s" target="_blank">Open Graph</a> metadata used in rich objects, such as embeds and search engine results. What is actually displayed is entirely up to the external service. Schemas are created when a post is first visited. Note that not all post types get a schema. You can set a default OG image under Site Identity in the <a href="%s" target="_blank">Customizer</a>.', 'fictioneer' ),
__( 'This is the generated <a href="%s" target="_blank">Open Graph</a> metadata used in rich objects, such as embeds and search engine results. What is actually displayed is entirely up to the external service. Schemas are created when a post is first visited. Note that not all post types or page templates get a schema. You can set a default OG image under Site Identity in the <a href="%s" target="_blank">Customizer</a>.', 'fictioneer' ),
'https://ogp.me/',
wp_customize_url()
);
?></p>
</div>
<div class="fictioneer-card__table">
<table>
<thead>
<tr>
<th class="cell-centered"><?php _e( 'OG Image', 'fictioneer' ); ?></th>
<th><?php _e( 'OG Title', 'fictioneer' ); ?></th>
<th><?php _e( 'OG Description & Schema', 'fictioneer' ); ?></th>
<th style="width: 120px;"><?php _e( 'Type', 'fictioneer' ); ?></th>
<th style="width: 160px;"><?php _e( 'Modified', 'fictioneer' ); ?></th>
</tr>
</thead>
<?php if ( fictioneer_seo_plugin_active() ) : ?>
<tbody>
<tr>
<td colspan="99"><?php _e( 'Disabled due to incompatible SEO plugin being active.', 'fictioneer' ); ?></td>
</tr>
</tbody>
<?php else : ?>
<tbody>
<?php foreach ( $post_ids as $post_id ) : ?>
<?php
$schema = get_post_meta( $post_id, 'fictioneer_schema', true );
$schema_text = stripslashes( json_encode( json_decode( $schema ), JSON_PRETTY_PRINT ) );
$image = get_post_meta( $post_id, 'fictioneer_seo_og_image_cache', true );
$image_url = $image ? $image['url'] : get_template_directory_uri() . '/img/no_image_placeholder.svg';
$type = get_post_type( $post_id );
$type_label = get_post_type_object( $type )->labels->singular_name;
$title = mb_strimwidth( fictioneer_get_seo_title( $post_id ), 0, 48, '…' );
$link = get_the_permalink( $post_id );
$seo_description = fictioneer_get_seo_description( $post_id );
?>
<tr id="schema-<?php echo $post_id; ?>">
<td>
<img src="<?php echo $image_url ?>" width="72" height="48">
</td>
<td class="cell-primary">
<a href="<?php echo $link; ?>"><?php echo $title; ?></a>
<div class="fictioneer-table-actions">
<span class="edit">
<a href="<?php edit_post_link( $post_id ); ?>">
<?php _e( 'Edit', 'fictioneer' ); ?>
</a>
</span>
<?php if ( $schema ) : ?>
<span class="delete">
|
<label class="button-purge-schema" data-id="<?php echo $post_id; ?>">
<?php _e( 'Purge Schema', 'fictioneer' ); ?>
</label>
</span>
<?php endif; ?>
</div>
</td>
<td class="cell-schema">
<?php if ( $schema ) : ?>
<details>
<summary><?php echo $seo_description; ?></summary>
<textarea rows="8" readonly><?php echo $schema_text; ?></textarea>
</details>
<?php else : ?>
<strong><?php _e( '[Empty]', 'fictioneer' ); ?></strong>
<?php echo $seo_description; ?>
<?php endif; ?>
</td>
<td class="cell-no-wrap"><?php echo $type_label ?></td>
<td class="cell-no-wrap"><?php
printf(
_x( '%1$s<br>at %2$s', 'Table date and time column.', 'fictioneer' ),
get_the_modified_date( get_option( 'date_format' ), $post_id ),
get_the_modified_date( get_option( 'time_format' ), $post_id )
);
?></td>
</tr>
<?php endforeach; ?>
</tbody>
<?php endif; ?>
</table>
</div>
</div>
</div>
</div>
<div class="fictioneer-actions">
<div class="fictioneer-actions__group">
<a class="button secondary" id="button-purge-all-schemas" data-click="purge-all-schemas" data-prompt="<?php esc_attr_e( 'Are you sure you want to purge all schemas? They will be regenerated on visit.', 'fictioneer' ); ?>" href="<?php echo $purge_all_url; ?>"><?php _e( 'Purge All Schemas', 'fictioneer' ); ?></a>
<a class="button secondary" id="button-purge-all-meta" data-click="purge-all-meta" data-prompt="<?php esc_attr_e( 'Are you sure you want to purge all SEO meta caches? They will be regenerated on visit.', 'fictioneer' ); ?>" href="<?php echo $purge_meta_url; ?>"><?php _e( 'Purge SEO Meta Caches', 'fictioneer' ); ?></a>
</div>
<div class="fictioneer-actions__group">
<?php
if ( $query->have_posts() ) {
fictioneer_admin_pagination( $per_page, $query->max_num_pages, $query->found_posts );
}
?>
</div>
<div>
<?php $seo_table->display(); ?>
</div>
</div>
</div>
<dialog class="fictioneer-dialog" id="schema-dialog">
<div class="fictioneer-dialog__header">
<span><?php _e( 'Schema', 'fictioneer' ); ?></span>
</div>
<div class="fictioneer-dialog__content">
<form>
<div class="fictioneer-dialog__row">
<textarea rows="16" data-target="schema-content" readonly></textarea>
</div>
<div class="fictioneer-dialog__actions">
<button value="cancel" formmethod="dialog" class="button"><?php _e( 'Close', 'fictioneer' ); ?></button>
</div>
</form>
</div>
</dialog>
</div>

2
js/admin.min.js vendored
View File

@ -1 +1 @@
function fcn_purgeSchema(e){jQuery.ajax({url:fictioneer_ajax.ajax_url,type:"post",data:{action:"fictioneer_ajax_purge_schema",nonce:document.getElementById("fictioneer_admin_nonce").value,id:e},dataType:"json",success:function(t){if(t.success){const t=_$$$(`schema-${e}`);t.querySelector(".cell-schema").innerHTML=t.querySelector("summary").innerHTML,t.querySelector(".delete").remove()}}})}function fcn_delete_epub(e){jQuery.ajax({url:fictioneer_ajax.ajax_url,type:"post",data:{action:"fictioneer_ajax_delete_epub",nonce:document.getElementById("fictioneer_admin_nonce").value,post_id:e},dataType:"json",success:function(t){t.success&&document.querySelector(`[data-delete-epub][data-id="${e}"]`).closest("tr").remove()}})}function fcn_ogMediaUpload(e){e.preventDefault();var t=wp.media({multiple:!1,library:{type:"image"}}).open().on("select",(function(){const e=t.state().get("selection").first().toJSON();_$$$("fictioneer-seo-og-image").value=e.id,_$$$("fictioneer-seo-og-display").setAttribute("src",e.url),_$$$("fictioneer-button-seo-og-image-remove").classList.remove("hidden"),_$(".og-source").classList.add("hidden")}))}function fcn_update_seo_title_chars(){const e=_$$$("fictioneer-seo-title");"{{title}} {{site}}"!=e.value?_$$$("fictioneer-seo-title-chars").innerHTML=`(${e.value.length}/70)`:_$$$("fictioneer-seo-title-chars").innerHTML=""}function fcn_remove_seo_og_image(e){e.preventDefault();const t=_$$$("fictioneer-seo-og-display").dataset.placeholder;_$$$("fictioneer-seo-og-image").value="",_$$$("fictioneer-seo-og-display").setAttribute("src",t),_$$$("fictioneer-button-seo-og-image-remove").classList.add("hidden")}function fcn_confirmIt(e){const t=e.currentTarget.dataset.dialogMessage,a=e.currentTarget.dataset.dialogConfirm;if(!t||!a)return;const o=prompt(t);o?o.toLowerCase()!=a.toLowerCase()&&e.preventDefault():e.preventDefault()}_$$(".button-purge-schema").forEach((e=>{e.addEventListener("click",(e=>{fcn_purgeSchema(e.currentTarget.dataset.id)}))})),_$$("[data-delete-epub]").forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault(),fcn_delete_epub(e.target.dataset.id)}))})),(button=_$$$("fictioneer-button-og-upload"))&&button.addEventListener("click",fcn_ogMediaUpload),(button=_$$$("fictioneer-seo-title"))&&(fcn_update_seo_title_chars(),button.addEventListener("keyup",fcn_update_seo_title_chars)),(button=_$$$("fictioneer-button-seo-og-image-remove"))&&button.addEventListener("click",fcn_remove_seo_og_image),_$$("[data-confirm-dialog]").forEach((e=>{e.addEventListener("click",(e=>{fcn_confirmIt(e)}))})),_$("#wp-admin-bar-logout a")?.addEventListener("click",(()=>{localStorage.removeItem("fcnProfileAvatar"),localStorage.removeItem("fcnUserData"),localStorage.removeItem("fcnAuth"),localStorage.removeItem("fcnBookshelfContent"),localStorage.removeItem("fcnChapterBookmarks")})),_$(".fictioneer-settings")?.addEventListener("click",(e=>{const t=e.target.closest("[data-click]"),a=t?.dataset.click;if(a)switch(a){case"purge-all-epubs":case"purge-all-schemas":case"purge-all-meta":case"reset-post-relationship-registry":confirm(t.dataset.prompt)||e.preventDefault()}})),_$$("[data-dialog-target]").forEach((e=>{e.addEventListener("click",(e=>{_$$$(e.currentTarget.dataset.dialogTarget)?.showModal()}))})),_$$('button[formmethod="dialog"][value="cancel"]').forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault(),e.currentTarget.closest("dialog").close()}))})),_$$("dialog").forEach((e=>{e.addEventListener("mousedown",(e=>{"dialog"===e.target.tagName.toLowerCase()&&e.target.close()}))}));
function fcn_toggleInProgress(e,t=null){(t=null!==t?t:!e.disabled)?(e.dataset.enableWith=e.innerHTML,e.innerHTML=e.dataset.disableWith,e.disabled=!0,e.classList.add("disabled")):(e.innerHTML=e.dataset.enableWith,e.disabled=!1,e.classList.remove("disabled"))}function fcn_purgeSchema(e){const t=_$(`a[data-id="${e}"]`),a=t.closest(".row-actions");t.closest("tr").classList.add("no-schema"),a.remove(),fcn_ajaxPost({action:"fictioneer_ajax_purge_schema",nonce:document.getElementById("fictioneer_admin_nonce").value,post_id:e}).then((e=>{e.success||console.log(e.data)})).catch((e=>{console.log(e)}))}function fcn_purgeAllSchemas(e=0,t=null,a=0){const o=_$$("[data-action-purge-all-schemas]");null===t&&o.forEach((e=>{fcn_toggleInProgress(e,!0)})),fcn_ajaxPost({action:"fictioneer_ajax_purge_all_schemas",offset:e,nonce:document.getElementById("fictioneer_admin_nonce").value}).then((e=>{if(e.data.finished)o.forEach((e=>{fcn_toggleInProgress(e,!1)})),_$$("tr").forEach((e=>{e.classList.add("no-schema")}));else{const t=parseInt(a/Math.max(e.data.total,1)*100),n=t>0?` ${t} %`:"";o.forEach((e=>{e.innerHTML=e.dataset.disableWith+n})),fcn_purgeAllSchemas(e.data.next_offset,e.data.total,a+e.data.processed)}})).catch((e=>{console.log(e)}))}function fcn_delete_epub(e){jQuery.ajax({url:fictioneer_ajax.ajax_url,type:"post",data:{action:"fictioneer_ajax_delete_epub",nonce:document.getElementById("fictioneer_admin_nonce").value,post_id:e},dataType:"json",success:function(t){t.success&&document.querySelector(`[data-delete-epub][data-id="${e}"]`).closest("tr").remove()}})}function fcn_ogMediaUpload(e){e.preventDefault();var t=wp.media({multiple:!1,library:{type:"image"}}).open().on("select",(function(){const e=t.state().get("selection").first().toJSON();_$$$("fictioneer-seo-og-image").value=e.id,_$$$("fictioneer-seo-og-display").setAttribute("src",e.url),_$$$("fictioneer-button-seo-og-image-remove").classList.remove("hidden"),_$(".og-source").classList.add("hidden")}))}function fcn_update_seo_title_chars(){const e=_$$$("fictioneer-seo-title");_$$$("fictioneer-seo-title-chars").innerHTML=`(${e.value.length}/70)`}function fcn_remove_seo_og_image(e){e.preventDefault();const t=_$$$("fictioneer-seo-og-display").dataset.placeholder;_$$$("fictioneer-seo-og-image").value="",_$$$("fictioneer-seo-og-display").setAttribute("src",t),_$$$("fictioneer-button-seo-og-image-remove").classList.add("hidden")}function fcn_confirmIt(e){const t=e.currentTarget.dataset.dialogMessage,a=e.currentTarget.dataset.dialogConfirm;if(!t||!a)return;const o=prompt(t);o?o.toLowerCase()!=a.toLowerCase()&&e.preventDefault():e.preventDefault()}_$$("[data-purge-schema]").forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault(),fcn_purgeSchema(e.currentTarget.dataset.id)}))})),_$$("[data-action-purge-all-schemas]").forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault(),fcn_purgeAllSchemas()}))})),_$$('[data-dialog-target="schema-dialog"]').forEach((e=>{e.addEventListener("click",(e=>{const t=_$('[data-target="schema-content"]');t.value=e.currentTarget.closest("tr").querySelector("[data-schema-id]").textContent,t.scrollTop=0,setTimeout((()=>{t.scrollTop=0}),10)}))})),_$$("[data-delete-epub]").forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault(),fcn_delete_epub(e.target.dataset.id)}))})),(button=_$$$("fictioneer-button-og-upload"))&&button.addEventListener("click",fcn_ogMediaUpload),(button=_$$$("fictioneer-seo-title"))&&(fcn_update_seo_title_chars(),button.addEventListener("keyup",fcn_update_seo_title_chars)),(button=_$$$("fictioneer-button-seo-og-image-remove"))&&button.addEventListener("click",fcn_remove_seo_og_image),_$$("[data-confirm-dialog]").forEach((e=>{e.addEventListener("click",(e=>{fcn_confirmIt(e)}))})),_$("#wp-admin-bar-logout a")?.addEventListener("click",(()=>{localStorage.removeItem("fcnProfileAvatar"),localStorage.removeItem("fcnUserData"),localStorage.removeItem("fcnAuth"),localStorage.removeItem("fcnBookshelfContent"),localStorage.removeItem("fcnChapterBookmarks")})),_$(".fictioneer-settings")?.addEventListener("click",(e=>{const t=e.target.closest("[data-click]"),a=t?.dataset.click;if(a)switch(a){case"purge-all-epubs":case"purge-all-schemas":case"purge-all-meta":case"reset-post-relationship-registry":confirm(t.dataset.prompt)||e.preventDefault()}})),_$$("[data-dialog-target]").forEach((e=>{e.addEventListener("click",(e=>{_$$$(e.currentTarget.dataset.dialogTarget)?.showModal()}))})),_$$('button[formmethod="dialog"][value="cancel"]').forEach((e=>{e.addEventListener("click",(e=>{e.preventDefault(),e.currentTarget.closest("dialog").close()}))})),_$$("dialog").forEach((e=>{e.addEventListener("mousedown",(e=>{"dialog"===e.target.tagName.toLowerCase()&&e.target.close()}))}));

View File

@ -1,47 +1,152 @@
/* =============================================================================
** SCHEMAS
** ========================================================================== */
// =============================================================================
// PROGRESSIVE ACTIONS
// =============================================================================
/**
* Purges a schema from a post.
* Toggles progression state of an element.
*
* @description Takes the ID of a post to delete the schema via AJAX.
* Removes the schema row if successful.
*
* @since 4.7
* @param {Number} id - ID of the post.
* @since 5.7.2
* @param {HTMLElement} element - The element.
* @param {Boolean|null} force - Whether to disable or enable. Defaults to
* the opposite of the current state.
*/
function fcn_purgeSchema(id) {
jQuery.ajax({
url: fictioneer_ajax.ajax_url,
type: 'post',
data: {
action: 'fictioneer_ajax_purge_schema',
nonce: document.getElementById('fictioneer_admin_nonce').value,
id: id
},
dataType: 'json',
success: function(response) {
if (response.success) {
const tr = _$$$(`schema-${id}`);
tr.querySelector('.cell-schema').innerHTML = tr.querySelector('summary').innerHTML;
tr.querySelector('.delete').remove();
}
function fcn_toggleInProgress(element, force = null) {
force = force !== null ? force : !element.disabled;
if (force) {
element.dataset.enableWith = element.innerHTML;
element.innerHTML = element.dataset.disableWith;
element.disabled = true;
element.classList.add('disabled');
} else {
element.innerHTML = element.dataset.enableWith;
element.disabled = false;
element.classList.remove('disabled');
}
}
// =============================================================================
// SCHEMAS
// =============================================================================
/**
* Purges the schema of a post.
*
* @since 5.7.2
* @param {Number} post_id - ID of the post.
*/
function fcn_purgeSchema(post_id) {
const button = _$(`a[data-id="${post_id}"]`);
const actions = button.closest('.row-actions');
const row = button.closest('tr');
row.classList.add('no-schema');
actions.remove();
// Request
fcn_ajaxPost({
'action': 'fictioneer_ajax_purge_schema',
'nonce': document.getElementById('fictioneer_admin_nonce').value,
'post_id': post_id
})
.then(response => {
if (!response.success) {
console.log(response.data);
}
})
.catch(error => {
console.log(error);
});
}
// Listen for clicks on schema purge buttons
_$$('.button-purge-schema').forEach(element => {
_$$('[data-purge-schema]').forEach(element => {
element.addEventListener(
'click',
(e) => {
fcn_purgeSchema(e.currentTarget.dataset.id)
event => {
event.preventDefault();
fcn_purgeSchema(event.currentTarget.dataset.id)
}
);
});
/**
* Purges the schema of all posts.
*
* @since 5.7.2
* @param {Number=0} offset - Offset of post IDs to query.
* @param {Number|null} total - The total number of posts to be processed.
* @param {Number=0} processed - The total number of posts processed so far.
*/
function fcn_purgeAllSchemas(offset = 0, total = null, processed = 0) {
// --- Setup ----------------------------------------------------------------
const buttons = _$$('[data-action-purge-all-schemas]');
if ( total === null ) {
buttons.forEach(element => {
fcn_toggleInProgress(element, true);
});
}
// --- Request ---------------------------------------------------------------
fcn_ajaxPost({
'action': 'fictioneer_ajax_purge_all_schemas',
'offset': offset,
'nonce': document.getElementById('fictioneer_admin_nonce').value
})
.then(response => {
if (!response.data.finished) {
const progress = parseInt(processed / Math.max(response.data.total, 1) * 100);
const part = progress > 0 ? ` ${progress} %` : '';
buttons.forEach(element => {
element.innerHTML = element.dataset.disableWith + part;
});
fcn_purgeAllSchemas(
response.data.next_offset,
response.data.total,
processed + response.data.processed
);
} else {
buttons.forEach(element => {
fcn_toggleInProgress(element, false);
});
_$$('tr').forEach(row => {
row.classList.add('no-schema');
});
}
})
.catch(error => {
console.log(error);
});
}
// Listen to click on "Purge All" buttons
_$$('[data-action-purge-all-schemas]').forEach(element => {
element.addEventListener('click', event => {
event.preventDefault();
fcn_purgeAllSchemas();
});
});
// Listen to click on schema dialog buttons
_$$('[data-dialog-target="schema-dialog"]').forEach(element => {
element.addEventListener('click', event => {
const target = _$('[data-target="schema-content"]');
target.value = event.currentTarget.closest('tr').querySelector('[data-schema-id]').textContent;
target.scrollTop = 0;
setTimeout(() => { target.scrollTop = 0; }, 10);
});
});
/* =============================================================================
** EPUBS
** ========================================================================== */
@ -128,11 +233,7 @@ if (button = _$$$('fictioneer-button-og-upload')) {
function fcn_update_seo_title_chars() {
const input = _$$$('fictioneer-seo-title');
if (input.value != '{{title}} {{site}}') {
_$$$('fictioneer-seo-title-chars').innerHTML = `(${input.value.length}/70)`;
} else {
_$$$('fictioneer-seo-title-chars').innerHTML = '';
}
_$$$('fictioneer-seo-title-chars').innerHTML = `(${input.value.length}/70)`;
}
// Initial call and listen for keyup input

View File

@ -79,6 +79,14 @@
}
}
a[data-dialog-target] {
cursor: pointer;
}
a.disabled {
pointer-events: none;
}
:where(h1, h2, h3, h4, h5, h6) {
line-height: 1.3;
margin: 0;
@ -215,13 +223,15 @@
}
}
table {
border-collapse: collapse;
width: 100%;
}
.table-view-list.epubs tr {
transition: opacity .3s, filter .3s;
:is(th, td) {
text-align: left;
&.no-schema {
> * {
opacity: .5;
filter: grayscale(.5);
}
}
}
// Reset table page input
@ -245,6 +255,28 @@
}
}
:is(.column-chapters, .column-words, .column-downloads, .column-size, .column-type) {
width: 13%;
@include bp(1200px) {
width: 10%;
}
}
.column-description {
font-size: 12px;
}
.row-thumbnail {
float: left;
margin-right: 10px;
object-fit: cover;
@include bp(783px) {
margin-right: 15px;
}
}
// =============================================================================
// LAYOUT
// =============================================================================
@ -380,149 +412,6 @@
}
}
&__table {
position: relative;
z-index: 1;
background: {
image: radial-gradient(farthest-side at 0 50%, rgb(0 0 0 / 15%), transparent),
radial-gradient(farthest-side at 100% 50%, rgb(0 0 0 / 15%), transparent);
position: -1px 0, calc(100% + 1px) 0;
size: 8px 100%;
repeat: no-repeat;
}
overflow-x: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
height: 0;
width: 0;
}
table {
position: relative;
&::before,
&::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
z-index: -1;
display: block;
width: 24px;
}
&::before {
left: 0;
background: linear-gradient(to right, var(--card-background-color), var(--card-background-color) 50%, transparent);
}
&::after {
right: 0;
background: linear-gradient(to left, var(--card-background-color), var(--card-background-color) 50%, transparent);
}
}
thead {
background: var(--table-row-background-color-head);
border-bottom: 1px solid var(--table-border-color);
}
&:not(:first-child) thead {
border-top: 1px solid var(--table-border-color);
}
tr:hover .fictioneer-table-actions {
opacity: 1;
}
tr:nth-child(even) {
background-color: var(--table-row-background-color-even);
}
th {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
white-space: nowrap;
vertical-align: bottom;
padding: 8px 10px;
}
td {
font-size: 12px;
overflow-wrap: break-word;
vertical-align: top;
padding: 10px;
}
a {
text-decoration: none;
}
img {
object-fit: cover;
}
textarea {
font-size: 11px;
}
.cell-primary {
> a {
font-weight: 600;
white-space: nowrap;
}
}
.cell-centered {
text-align: center;
}
.cell-no-wrap {
white-space: nowrap;
}
.cell-schema {
min-width: 280px;
@include bp(375px) {
min-width: 335px;
}
@include bp(420px) {
min-width: 380px;
}
@include bp(768px) {
min-width: 480px;
}
}
.row-nothing {
&:not(:first-child) {
display: none;
}
}
.fictioneer-table-actions {
color: #a7aaad;
font-size: 12px;
font-weight: 400;
padding-top: 4px;
transition: opacity .1s;
@media only screen and (min-width: 1200px) {
opacity: 0;
}
.delete > :is(a, label, button) {
color: #b32d2e;
}
}
}
&__footer {
padding: 12px;
}
@ -731,4 +620,8 @@
padding-top: 16px;
}
}
textarea[data-target="schema-content"] {
font-size: 10px;
}
}