Add story changelog

This commit is contained in:
Tetrakern 2023-11-08 20:03:19 +01:00
parent 24ffffbbc0
commit 1f0f30a1d8
17 changed files with 383 additions and 15 deletions

View File

@ -241,13 +241,15 @@ Fictioneer customizes WordPress by using as many standard action and filter hook
| `manage_comments_custom_column` | `fictioneer_add_comments_report_column_content`
| `personal_options_update` | `fictioneer_update_admin_user_profile`, `fictioneer_update_my_user_profile`
| `pre_get_posts` | `fictioneer_extend_search_query`, `fictioneer_remove_unlisted_from_search`, `fictioneer_read_others_files`, `fictioneer_read_others_files_list_view`, `fictioneer_filter_chapters_by_story`
| `publish_to_draft` | `fictioneer_chapter_publish_to_draft`
| `rest_api_init` | `fictioneer_register_endpoint_get_story_comments`
| `restrict_manage_posts` | `fictioneer_add_chapter_story_filter_dropdown`
| `save_post` | `fictioneer_create_sitemap`, `fictioneer_refresh_chapters_schema`, `fictioneer_refresh_chapter_schema`, `fictioneer_refresh_collections_schema`, `fictioneer_refresh_post_caches`, `fictioneer_refresh_post_schema`, `fictioneer_refresh_recommendations_schema`, `fictioneer_refresh_recommendation_schema`, `fictioneer_refresh_stories_schema`, `fictioneer_refresh_story_schema`, `fictioneer_save_seo_metabox`, `fictioneer_save_word_count`, `fictioneer_track_chapter_and_story_updates`, `fictioneer_update_modified_date_on_story_for_chapter`, `fictioneer_update_shortcode_relationships`, `fictioneer_purge_transients`, `fictioneer_post_story_to_discord`, `fictioneer_post_chapter_to_discord`, `fictioneer_save_story_metaboxes`, `fictioneer_save_chapter_metaboxes`, `fictioneer_save_advanced_metabox`, `fictioneer_save_support_links_metabox`, `fictioneer_save_collection_metaboxes`, `fictioneer_save_recommendation_metaboxes`, `fictioneer_save_post_metaboxes`
| `show_user_profile` | `fictioneer_custom_profile_fields`
| `switch_theme` | `fictioneer_theme_deactivation`
| `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`
| `transition_post_status` | `fictioneer_log_story_chapter_status_changes`
| `trashed_post` | `fictioneer_refresh_post_caches`, `fictioneer_track_chapter_and_story_updates`, `fictioneer_update_modified_date_on_story_for_chapter`, `fictioneer_purge_transients`, `fictioneer_remove_chapter_from_story`
| `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_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`

View File

@ -587,6 +587,7 @@ Most of the themes configuration is found here, the options being largely sel
* **Enable AJAX comment form/section:** If you have trouble with caching. Try the form first to save resources.
* **Enable AJAX user authentication:** If you have trouble with [Nonces](https://developer.wordpress.org/apis/security/nonces/) and/or users not being properly logged-in. Use this as *last resort* to bypass the cache.
* **Disable theme comment {…}:** If you want to use different comments. Disables most of the other comment options as well.
* **Show story changelog button:** Opens modal with timestamped chapter changes; located under the chapter list.
<br>
@ -924,3 +925,4 @@ define( 'CONSTANT_NAME', value );
| FICTIONEER_ENABLE_FRONTEND_ACF | boolean | Whether to load ACF on the frontend. Default `false`.
| FICTIONEER_ENABLE_MENU_TRANSIENTS | boolean | Whether to cache nav menus as Transients. Default `true`.
| FICTIONEER_ORDER_STORIES_BY_LATEST_CHAPTER | boolean | Whether to order updated stories based on the latest chapter added, excluding stories without chapters. Default `false`.
| FICTIONEER_ENABLE_STORY_CHANGELOG | boolean | Whether changes to the story chapter list should be logged. Default `true`.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -356,6 +356,11 @@ if ( ! defined( 'FICTIONEER_ORDER_STORIES_BY_LATEST_CHAPTER' ) ) {
define( 'FICTIONEER_ORDER_STORIES_BY_LATEST_CHAPTER', false );
}
// Boolean: Enable tracking of chapter changes in stories
if ( ! defined( 'FICTIONEER_ENABLE_STORY_CHANGELOG' ) ) {
define( 'FICTIONEER_ENABLE_STORY_CHANGELOG', true );
}
// =============================================================================
// FAST REQUESTS
//

View File

@ -463,6 +463,9 @@ function fictioneer_remember_chapters_modified( $value, $post_id ) {
update_post_meta( $post_id, 'fictioneer_chapters_added', current_time( 'mysql' ) );
}
// Log changes
fictioneer_log_story_chapter_changes( $post_id, $value, $previous );
return $value;
}
add_filter( 'acf/update_value/name=fictioneer_story_chapters', 'fictioneer_remember_chapters_modified', 10, 2 );

View File

@ -229,12 +229,13 @@ if ( ! function_exists( 'fictioneer_get_safe_title' ) ) {
* @since Fictioneer 4.7
* @link https://developer.wordpress.org/reference/functions/wp_strip_all_tags/
*
* @param int|WP_Post $post The post or post ID to get the title for.
* @param int|WP_Post $post The post or post ID to get the title for.
* @param boolean $no_filters Optional. Whether to ignore filters. Default false.
*
* @return string The title, never empty.
*/
function fictioneer_get_safe_title( $post ) {
function fictioneer_get_safe_title( $post, $no_filters = false ) {
// Setup
$post_id = ( $post instanceof WP_Post ) ? $post->ID : $post;
@ -251,7 +252,9 @@ if ( ! function_exists( 'fictioneer_get_safe_title' ) ) {
}
// Apply filters
$title = apply_filters( 'fictioneer_filter_safe_title', $title, $post_id );
if ( ! $no_filters ) {
$title = apply_filters( 'fictioneer_filter_safe_title', $title, $post_id );
}
return $title;
}

View File

@ -1866,7 +1866,7 @@ function fictioneer_save_chapter_metaboxes( $post_id ) {
add_action( 'save_post', 'fictioneer_save_chapter_metaboxes' );
/**
* Append new chapters to story list
* Appends new chapters to story list
*
* @since Fictioneer 5.4.9
* @since Fictioneer 5.7.4 - Updated
@ -1915,15 +1915,20 @@ function fictioneer_append_chapter_to_story( $post_id, $story_id ) {
// Append chapter (if not already included) and save to database
if ( ! in_array( $post_id, $story_chapters ) ) {
$previous_chapters = $story_chapters;
$story_chapters[] = $post_id;
$story_chapters = array_unique( $story_chapters );
// Append chapter to field
update_post_meta( $story_id, 'fictioneer_story_chapters', array_unique( $story_chapters ) );
update_post_meta( $story_id, 'fictioneer_story_chapters', $story_chapters );
// Remember when chapter list has been last updated
update_post_meta( $story_id, 'fictioneer_chapters_modified', current_time( 'mysql' ) );
update_post_meta( $story_id, 'fictioneer_chapters_added', current_time( 'mysql' ) );
// Log changes
fictioneer_log_story_chapter_changes( $story_id, $story_chapters, $previous_chapters );
// Clear story data cache to ensure it gets refreshed
delete_post_meta( $story_id, 'fictioneer_story_data_collection' );
} else {

View File

@ -106,4 +106,234 @@ function fictioneer_store_original_publish_date( $post_id, $post ) {
}
add_action( 'save_post', 'fictioneer_store_original_publish_date', 10, 2 );
// =============================================================================
// STORY CHANGELOG
// =============================================================================
/**
* Returns story changelog
*
* @since Fictioneer 5.7.5
*
* @param int $story_id The story post ID.
*
* @return array Array with logged chapter changes since initialization.
*/
function fictioneer_get_story_changelog( $story_id ) {
// Setup
$changelog = get_post_meta( $story_id, 'fictioneer_story_changelog', true );
$changelog = is_array( $changelog ) ? $changelog : [];
// Initialize
if ( empty( $changelog ) ) {
$changelog[] = array(
time(),
_x( 'Initialized.', 'Story changelog initialized.', 'fictioneer' )
);
}
// Return
return $changelog;
}
/**
* Logs changes to story chapters
*
* @since Fictioneer 5.7.5
*
* @param int $story_id The story post ID.
* @param array $current Current chapters.
* @param array $previous Previous chapters.
* @param string $verb Optional. The verb describing the logged action.
*/
function fictioneer_log_story_chapter_changes( $story_id, $current, $previous, $verb = null ) {
if ( ! FICTIONEER_ENABLE_STORY_CHANGELOG ) {
return;
}
// Setup
$changelog = fictioneer_get_story_changelog( $story_id );
$current = is_array( $current ) ? $current : [];
$previous = is_array( $previous ) ? $previous : [];
// Check for changes
$added = array_diff( $current, $previous );
$removed = array_diff( $previous, $current );
// Log
foreach ( $added as $post_id ) {
$changelog[] = array(
time(),
sprintf(
_x( '#%s %s: %s.', 'Story changelog chapter added.', 'fictioneer' ),
$post_id,
$verb ? $verb : _x( 'added', 'Story changelog verb.', 'fictioneer' ),
fictioneer_get_safe_title( $post_id )
)
);
}
foreach ( $removed as $post_id ) {
$changelog[] = array(
time(),
sprintf(
_x( '#%s %s: %s.', 'Story changelog chapter removed.', 'fictioneer' ),
$post_id,
$verb ? $verb : _x( 'removed', 'Story changelog verb.', 'fictioneer' ),
fictioneer_get_safe_title( $post_id )
)
);
}
// Save
update_post_meta( $story_id, 'fictioneer_story_changelog', $changelog );
}
/**
* Logs status changes of story chapters
*
* @since Fictioneer 5.7.5
*
* @param string $new_status The old status.
* @param string $old_status The new status.
* @param WP_Post $post The post object.
*/
function fictioneer_log_story_chapter_status_changes( $new_status, $old_status, $post ) {
// Changed?
if ( $old_status == $new_status ) {
return;
}
// Chapter?
if ( $post->post_type !== 'fcn_chapter' ) {
return;
}
// Story?
$story_id = fictioneer_get_field( 'fictioneer_chapter_story', $post->ID );
if ( empty( $story_id ) ) {
return;
}
// Log
$changelog = fictioneer_get_story_changelog( $story_id );
// Add filters
add_filter( 'private_title_format', 'fictioneer__return_no_format', 99 );
// Publish -> Private?
if ( $old_status == 'publish' && $new_status == 'private' ) {
$changelog[] = array(
time(),
sprintf(
_x( '#%s privated: %s.', 'Story changelog chapter removed.', 'fictioneer' ),
$post->ID,
fictioneer_get_safe_title( $post->ID, true )
)
);
update_post_meta( $story_id, 'fictioneer_story_changelog', $changelog );
}
// Private -> Publish?
if ( $new_status == 'publish' && $old_status == 'private' ) {
$changelog[] = array(
time(),
sprintf(
_x( '#%s unprivated: %s.', 'Story changelog chapter removed.', 'fictioneer' ),
$post->ID,
fictioneer_get_safe_title( $post->ID, true )
)
);
update_post_meta( $story_id, 'fictioneer_story_changelog', $changelog );
}
// Remove filters
remove_filter( 'private_title_format', 'fictioneer__return_no_format', 99 );
}
if ( FICTIONEER_ENABLE_STORY_CHANGELOG ) {
add_action( 'transition_post_status', 'fictioneer_log_story_chapter_status_changes', 10, 3 );
}
// =============================================================================
// STORY CHAPTER LIST
// =============================================================================
/**
* Removes chapter from story
*
* @since Fictioneer 5.7.5
*
* @param int $chapter_id The chapter post ID.
*/
function fictioneer_remove_chapter_from_story( $chapter_id ) {
// Chapter?
if ( get_post_type( $chapter_id ) !== 'fcn_chapter' ) {
return;
}
// Story?
$story_id = fictioneer_get_field( 'fictioneer_chapter_story', $chapter_id );
if ( empty( $story_id ) ) {
return;
}
// Chapter list?
$chapters = fictioneer_get_field( 'fictioneer_story_chapters', $story_id );
$previous = fictioneer_get_field( 'fictioneer_story_chapters', $story_id );
if ( empty( $chapters ) || ! in_array( $chapter_id, $chapters ) ) {
return;
}
// Update story
$chapters = fictioneer_unset_by_value( $chapter_id, $chapters );
update_post_meta( $story_id, 'fictioneer_story_chapters', $chapters );
update_post_meta( $story_id, 'fictioneer_chapters_modified', current_time( 'mysql' ) );
// Log change
fictioneer_log_story_chapter_changes( $story_id, $chapters, $previous );
// Clear story data cache to ensure it gets refreshed
delete_post_meta( $story_id, 'fictioneer_story_data_collection' );
// Update story post to fire associated actions
wp_update_post( array( 'ID' => $story_id ) );
}
add_action( 'trashed_post', 'fictioneer_remove_chapter_from_story' );
/**
* Removes chapter from story when unpublished
*
* @since Fictioneer 5.7.5
*
* @param WP_Post $post The post object.
*/
function fictioneer_chapter_publish_to_draft( $post ) {
// Chapter?
if ( $post->post_type !== 'fcn_chapter' ) {
return;
}
// Remove filter
remove_filter( 'fictioneer_filter_safe_title', 'fictioneer_prefix_draft_safe_title' );
// Remove chapter from story
fictioneer_remove_chapter_from_story( $post->ID );
// Add filter
add_filter( 'fictioneer_filter_safe_title', 'fictioneer_prefix_draft_safe_title', 10, 2 );
}
add_action( 'publish_to_draft', 'fictioneer_chapter_publish_to_draft' );
?>

View File

@ -128,8 +128,10 @@ add_action( 'fictioneer_site_footer', 'fictioneer_footer_menu_row', 20 );
*/
function fictioneer_output_modals( $args ) {
$is_archive = is_search() || is_archive() || empty( $args['post_id'] );
// Formatting and suggestions
if ( ! empty( $args['post_id'] ) && $args['post_type'] == 'fcn_chapter' ) {
if ( ! $is_archive && $args['post_type'] == 'fcn_chapter' ) {
?><input id="modal-formatting-toggle" data-target="formatting-modal" type="checkbox" tabindex="-1" class="modal-toggle" autocomplete="off" hidden><?php
get_template_part( 'partials/_modal-formatting' );
@ -171,6 +173,17 @@ function fictioneer_output_modals( $args ) {
get_template_part( 'partials/_modal-bbcodes' );
}
// Story chapter changelog
if (
! $is_archive &&
$args['post_type'] == 'fcn_story' &&
FICTIONEER_ENABLE_STORY_CHANGELOG &&
get_option( 'fictioneer_show_story_changelog' )
) {
?><input id="modal-chapter-changelog-toggle" data-target="chapter-changelog-modal" type="checkbox" tabindex="-1" class="modal-toggle" autocomplete="off" hidden><?php
get_template_part( 'partials/_modal-chapter-changelog' );
}
// Action to add modals
do_action( 'fictioneer_modals' );
}

View File

@ -628,6 +628,13 @@ define( 'FICTIONEER_OPTIONS', array(
'sanitize_callback' => 'fictioneer_sanitize_checkbox',
'label' => __( 'Enable advanced meta fields', 'fictioneer' ),
'default' => false
),
'fictioneer_show_story_changelog' => array(
'name' => 'fictioneer_show_story_changelog',
'group' => 'fictioneer-settings-general-group',
'sanitize_callback' => 'fictioneer_sanitize_checkbox',
'label' => __( 'Show story changelog button', 'fictioneer' ),
'default' => false
)
),
'integers' => array(

View File

@ -139,7 +139,7 @@
<div class="fictioneer-card__content">
<div class="fictioneer-card__row">
<p><?php _e( 'Updating these settings may require the story caches to be purged. You can do that under Tools.', 'fictioneer' ); ?></p>
<p><?php _e( 'Updating these settings may require the theme caches to be purged. You can do that under Tools.', 'fictioneer' ); ?></p>
</div>
<div class="fictioneer-card__row">
@ -187,6 +187,15 @@
?>
</div>
<div class="fictioneer-card__row">
<?php
fictioneer_label_checkbox(
'fictioneer_show_story_changelog',
__( 'Opens modal with timestamped chapter changes.', 'fictioneer' )
);
?>
</div>
<div class="fictioneer-card__row fictioneer-card__row--inline-input">
<p class="fictioneer-inline-text-input"><?php
printf(
@ -610,7 +619,7 @@
</div>
<div class="fictioneer-card__row">
<?php fictioneer_textarea( 'fictioneer_comments_notice', '158px' ); ?>
<?php fictioneer_textarea( 'fictioneer_comments_notice', '214px' ); ?>
</div>
</div>

View File

@ -0,0 +1,39 @@
<?php
/**
* Partial: Chapter Changelog
*
* @package WordPress
* @subpackage Fictioneer
* @since 5.7.5
*/
?>
<?php
// No direct access!
defined( 'ABSPATH' ) OR exit;
// Setup
$changelog = array_reverse( fictioneer_get_story_changelog( get_the_ID() ) );
$output = '';
// Prepare changelog
foreach ( $changelog as $entry ) {
$output .= '[' . date_i18n( 'Y/m/d H:i:s', $entry[0] ) . '] ' . $entry[1] . "\n";
}
?>
<div id="chapter-changelog-modal" class="chapter-changelog modal">
<label for="modal-chapter-changelog-toggle" class="background-close"></label>
<div class="modal__wrapper">
<label class="close" for="modal-chapter-changelog-toggle" tabindex="0" aria-label="<?php esc_attr_e( 'Close modal', 'fictioneer' ); ?>">
<?php fictioneer_icon( 'fa-xmark' ); ?>
</label>
<h4 class="modal__header drag-anchor"><?php _e( 'Changelog', 'fictioneer' ); ?></h4>
<div class="modal__row _textarea">
<textarea class="modal__textarea _changelog" readonly><?php echo $output; ?></textarea>
</div>
</div>
</div>

View File

@ -36,6 +36,13 @@ $story = $args['story_data'];
?>
<footer class="story__footer padding-left padding-right">
<div class="story__extra">
<?php if ( FICTIONEER_ENABLE_STORY_CHANGELOG && get_option( 'fictioneer_show_story_changelog' ) ) : ?>
<label class="story__changelog hide-below-400" for="modal-chapter-changelog-toggle" tabindex="-1">
<i class="fa-solid fa-clock-rotate-left"></i>
</label>
<?php endif; ?>
</div>
<div class="story__meta">
<span class="story__meta-item story__status">
<i class="<?php echo $story['icon']; ?>"></i>

View File

@ -295,8 +295,8 @@ $header_breakpoint: 640px;
line-height: 1.125;
text-align: center;
padding: 1.125rem 0;
transition: color var(--transition-duration);
width: 100%;
transition: color var(--transition-duration);
&:hover {
color: var(--fg-700);
@ -305,10 +305,29 @@ $header_breakpoint: 640px;
}
&__footer {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
padding-top: .75rem;
padding-bottom: 1.75rem;
}
&__changelog {
cursor: pointer;
display: grid;
place-content: center;
color: var(--fg-600);
font-size: var(--fs-ds);
height: 2.25rem;
width: 1.5rem;
transition: opacity var(--transition-duration);
&:not(:hover) {
opacity: 0.15;
}
}
&__meta {
display: flex;
align-items: center;
@ -350,7 +369,7 @@ $header_breakpoint: 640px;
}
}
> span {
> :is(span, label) {
:is(i, .icon) {
display: none;
color: var(--fg-800);

View File

@ -1,4 +1,5 @@
.modal {
--modal-width: 300px;
position: fixed;
inset: 0;
z-index: 1000;
@ -62,7 +63,8 @@
background: var(--modal-background);
border-radius: var(--layout-border-radius-large);
max-height: calc(100% - 20px);
width: 300px;
width: var(--modal-width);
max-width: calc(100vw - 20px);
box-shadow: var(--floating-shadow);
overflow: auto;
transform: translate(-50%, -50%);
@ -75,6 +77,7 @@
::-webkit-scrollbar {
width: 6px;
height: 6px;
border-radius: 1px;
}
@ -224,6 +227,23 @@
}
}
&__textarea {
display: block;
background: transparent;
font-family: var(--ff-mono);
font-size: 13px;
padding: 0;
width: 100%;
resize: none;
&._changelog {
font-size: 11px;
line-height: 20px;
white-space: pre;
height: 300px;
}
}
&__actions {
display: flex;
justify-content: flex-end;
@ -236,3 +256,7 @@
}
}
}
.chapter-changelog {
--modal-width: 450px;
}