Start using REST API; story comments

At least for everything that cannot use the fast AJAX, which is still superior.
This commit is contained in:
Tetrakern 2023-08-21 21:32:52 +02:00
parent e68e30fec1
commit 662e78bf5b
10 changed files with 131 additions and 56 deletions

View File

@ -218,6 +218,11 @@ if ( ! defined( 'FICTIONEER_STORY_COMMENT_COUNT_TIMEOUT' ) ) {
* Booleans
*/
// Prevent warnings
if ( ! defined( 'WP_DEBUG' ) ) {
define( 'WP_DEBUG', false );
}
// Boolean: Theme cache purging on post update
if ( ! defined( 'FICTIONEER_CACHE_PURGE_ASSIST' ) ) {
define( 'FICTIONEER_CACHE_PURGE_ASSIST', true );
@ -550,28 +555,11 @@ if ( get_option( 'fictioneer_enable_oauth' ) ) {
*/
require_once __DIR__ . '/includes/functions/comments/_comments_controller.php';
if ( is_admin() ) {
// Required for AJAX
require_once __DIR__ . '/includes/functions/comments/_story_comments.php';
require_once __DIR__ . '/includes/functions/comments/_comments_ajax.php';
require_once __DIR__ . '/includes/functions/comments/_comments_form.php';
require_once __DIR__ . '/includes/functions/comments/_comments_threads.php';
require_once __DIR__ . '/includes/functions/comments/_comments_moderation.php';
}
function fictioneer_conditional_require_comments() {
if ( is_singular( 'fcn_story' ) ) {
require_once __DIR__ . '/includes/functions/hooks/_story_hooks.php';
}
if ( comments_open() || is_singular( 'fcn_story' ) ) {
require_once __DIR__ . '/includes/functions/comments/_comments_form.php';
require_once __DIR__ . '/includes/functions/comments/_comments_threads.php';
require_once __DIR__ . '/includes/functions/comments/_comments_moderation.php';
}
}
add_action( 'wp', 'fictioneer_conditional_require_comments' );
require_once __DIR__ . '/includes/functions/comments/_comments_ajax.php';
require_once __DIR__ . '/includes/functions/comments/_comments_form.php';
require_once __DIR__ . '/includes/functions/comments/_comments_threads.php';
require_once __DIR__ . '/includes/functions/comments/_comments_moderation.php';
require_once __DIR__ . '/includes/functions/comments/_story_comments.php';
/**
* Add functions for users.

View File

@ -272,7 +272,7 @@ if ( ! function_exists( 'fictioneer_api_request_story' ) ) {
new WP_Error(
'invalid_story_id',
'No valid story was found matching the ID.',
['status' => '400']
array( 'status' => '400' )
)
);
}

View File

@ -554,6 +554,7 @@ function fictioneer_add_custom_scripts() {
// Localize application
wp_localize_script( 'fictioneer-application-scripts', 'fictioneer_ajax', array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'rest_url' => get_rest_url( null, 'fictioneer/v1/' ),
'ttl' => FICTIONEER_AJAX_TTL,
'login_ttl' => FICTIONEER_AJAX_LOGIN_TTL,
'post_debounce_rate' => FICTIONEER_AJAX_POST_DEBOUNCE_RATE

View File

@ -508,9 +508,11 @@ if ( ! function_exists( 'fictioneer_validate_id' ) ) {
function fictioneer_validate_id( $id, $for_type = [] ) {
$safe_id = intval( $id );
$types = is_array( $for_type ) ? $for_type : [$for_type];
$types = is_array( $for_type ) ? $for_type : [ $for_type ];
if ( empty( $safe_id ) || $safe_id < 0 ) return false;
if ( empty( $safe_id ) || $safe_id < 0 ) {
return false;
}
if ( ! empty( $for_type ) && ! in_array( get_post_type( $safe_id ), $types ) ) {
return false;

View File

@ -80,29 +80,80 @@ if ( ! function_exists( 'fictioneer_build_story_comment' ) ) {
}
// =============================================================================
// SEND STORY COMMENTS - AJAX
// SEND STORY COMMENTS - REST
// =============================================================================
/**
* Sends a block of comments for a given story via AJAX
* Register REST API endpoint to fetch story comments
*
* @since Fictioneer 4.0
* Endpoint URL: /wp-json/fictioneer/v1/get_story_comments
*
* Expected Parameters:
* - post_id (required): The ID of the story post.
* - page (optional): The page number for pagination. Default 1.
*
* @since Fictioneer 5.6.3
*/
function fictioneer_ajax_request_story_comments() {
// Nonce
check_ajax_referer( 'fictioneer_nonce', 'nonce' );
function fictioneer_register_endpoint_get_story_comments() {
register_rest_route(
'fictioneer/v1',
'/get_story_comments',
array(
'methods' => 'GET',
'callback' => 'fictioneer_rest_get_story_comments',
'args' => array(
'post_id' => array(
'validate_callback' => function( $param, $request, $key ) {
return is_numeric( $param );
},
'sanitize_callback' => 'absint',
'required' => true
),
'page' => array(
'validate_callback' => function( $param, $request, $key ) {
return is_numeric( $param );
},
'sanitize_callback' => 'absint',
'default' => 1
)
),
'permission_callback' => '__return_true' // Public
)
);
}
add_action( 'rest_api_init', 'fictioneer_register_endpoint_get_story_comments' );
/**
* Sends HTML of paginated comments for a given story
*
* @since Fictioneer 4.0
* @since Fictioneer 5.6.3 Refactored for REST API.
*
* @param WP_REST_Request $WP_REST_Request Request object.
*
* @return WP_REST_Response|WP_Error Response or error.
*/
function fictioneer_rest_get_story_comments( WP_REST_Request $request ) {
// Validations
$story_id = isset( $_GET['post_id'] ) ? fictioneer_validate_id( $_GET['post_id'], 'fcn_story' ) : false;
$story_id = $request->get_param( 'post_id' );
$story_id = isset( $story_id ) ? fictioneer_validate_id( $_GET['post_id'], 'fcn_story' ) : false;
$default_error = array( 'error' => __( 'Comments could not be loaded.', 'fictioneer' ) );
// Abort if not a story
if ( ! $story_id ) {
wp_send_json_error( array( 'error' => __( 'Comments could not be loaded.', 'fictioneer' ) ) );
if ( WP_DEBUG ) {
$data = array( 'error' => __( 'Provided post ID is not a story ID.', 'fictioneer' ) );
} else {
$data = $default_error;
}
return rest_ensure_response( array( 'data' => $data, 'success' => false ), 400 );
}
// Setup
$page = isset( $_GET['page'] ) ? absint( $_GET['page'] ) : 1;
$page = $request->get_param( 'page' );
$story = fictioneer_get_story_data( $story_id, true, array( 'force_comment_count_refresh' => true ) );
$chapter_ids = $story['chapter_ids']; // Only contains publicly visible chapters
$comments_per_page = get_option( 'comments_per_page' );
@ -111,7 +162,13 @@ function fictioneer_ajax_request_story_comments() {
// Abort if no chapters have been found
if ( count( $chapter_ids ) < 1 ) {
wp_send_json_error( array( 'error' => __( 'No chapters with comments.', 'fictioneer' ) ) );
if ( WP_DEBUG ) {
$data = array( 'error' => __( 'There are no valid chapters with comments.', 'fictioneer' ) );
} else {
$data = $default_error;
}
return rest_ensure_response( array( 'data' => $data, 'success' => false ) );
}
// Collect titles and permalinks of all chapters
@ -224,10 +281,11 @@ function fictioneer_ajax_request_story_comments() {
// Get buffer
$output = fictioneer_minify_html( ob_get_clean() );
// Response body (compatible to wp_send_json_success())
$data = array( 'html' => $output, 'postId' => $story_id, 'page' => $page );
// Return buffer
wp_send_json_success( array( 'html' => $output, 'postId' => $story_id, 'page' => $page ) );
return rest_ensure_response( array( 'data' => $data, 'success' => true ) );
}
add_action( 'wp_ajax_nopriv_fictioneer_ajax_request_story_comments', 'fictioneer_ajax_request_story_comments' );
add_action( 'wp_ajax_fictioneer_ajax_request_story_comments', 'fictioneer_ajax_request_story_comments' );
?>

2
js/story.min.js vendored
View File

@ -1 +1 @@
var fcn_storyCommentPage=1,fcn_storySettings=fcn_getStorySettings();function fcn_getStorySettings(){let t=localStorage.getItem("fcnStorySettings");return t=t&&fcn_isValidJSONString(t)?JSON.parse(t):fcn_defaultStorySettings(),(!t.hasOwnProperty("timestamp")||t.timestamp<1674770712849)&&(t=fcn_defaultStorySettings(),t.timestamp=Date.now()),fcn_setStorySettings(t),t}function fcn_defaultStorySettings(){return{view:"list",order:"asc",timestamp:1674770712849}}function fcn_setStorySettings(t){"object"==typeof t&&(fcn_storySettings=t,localStorage.setItem("fcnStorySettings",JSON.stringify(t)))}function fcn_applyStorySettings(){"object"==typeof fcn_storySettings&&(_$$("[data-view]").forEach((t=>{t.dataset.view="grid"==fcn_storySettings.view?"grid":"list"})),_$$("[data-order]").forEach((t=>{t.dataset.order="desc"==fcn_storySettings.order?"desc":"asc"})))}function fcn_toggleStoryTab(t){_$$(".story__article ._current").forEach((t=>{t.classList.remove("_current")})),_$$$(t.dataset.target).classList.add("_current"),_$$$("tabs").dataset.current=t.dataset.target,t.classList.add("_current")}function fcn_loadStoryComments(){let t;_$(".load-more-list-item").remove(),_$(".comments-loading-placeholder").classList.remove("hidden"),fcn_ajaxGet({action:"fictioneer_ajax_request_story_comments",post_id:fcn_inlineStorage.postId,page:fcn_storyCommentPage}).then((e=>{e.success?(_$(".fictioneer-comments__list > ul").innerHTML+=e.data.html,fcn_storyCommentPage++):e.data?.error&&(t=fcn_buildErrorNotice(e.data.error))})).catch((e=>{t=fcn_buildErrorNotice(e)})).then((()=>{_$(".comments-loading-placeholder").remove(),t&&_$(".fictioneer-comments__list > ul").appendChild(t)}))}fcn_applyStorySettings(),_$$$("button-toggle-chapter-order")?.addEventListener("click",(t=>{fcn_storySettings.order="asc"===t.currentTarget.dataset.order?"desc":"asc",fcn_setStorySettings(fcn_storySettings),fcn_applyStorySettings()})),_$$$("button-toggle-chapter-view")?.addEventListener("click",(t=>{fcn_storySettings.view="list"===t.currentTarget.dataset.view?"grid":"list",fcn_setStorySettings(fcn_storySettings),fcn_applyStorySettings()})),_$$(".chapter-group__folding-toggle").forEach((t=>{t.addEventListener("click",(t=>{const e=t.currentTarget.closest(".chapter-group[data-folded]");e&&(e.dataset.folded="true"==e.dataset.folded?"false":"true")}))})),_$$(".tabs__item").forEach((t=>{t.addEventListener("click",(t=>{fcn_toggleStoryTab(t.currentTarget)}))})),_$(".comment-section")?.addEventListener("click",(t=>{t.target?.classList.contains("load-more-comments-button")&&fcn_loadStoryComments()}));
var fcn_storyCommentPage=1,fcn_storySettings=fcn_getStorySettings();function fcn_getStorySettings(){let t=localStorage.getItem("fcnStorySettings");return t=t&&fcn_isValidJSONString(t)?JSON.parse(t):fcn_defaultStorySettings(),(!t.hasOwnProperty("timestamp")||t.timestamp<1674770712849)&&(t=fcn_defaultStorySettings(),t.timestamp=Date.now()),fcn_setStorySettings(t),t}function fcn_defaultStorySettings(){return{view:"list",order:"asc",timestamp:1674770712849}}function fcn_setStorySettings(t){"object"==typeof t&&(fcn_storySettings=t,localStorage.setItem("fcnStorySettings",JSON.stringify(t)))}function fcn_applyStorySettings(){"object"==typeof fcn_storySettings&&(_$$("[data-view]").forEach((t=>{t.dataset.view="grid"==fcn_storySettings.view?"grid":"list"})),_$$("[data-order]").forEach((t=>{t.dataset.order="desc"==fcn_storySettings.order?"desc":"asc"})))}function fcn_toggleStoryTab(t){_$$(".story__article ._current").forEach((t=>{t.classList.remove("_current")})),_$$$(t.dataset.target).classList.add("_current"),_$$$("tabs").dataset.current=t.dataset.target,t.classList.add("_current")}function fcn_loadStoryComments(){let t;_$(".load-more-list-item").remove(),_$(".comments-loading-placeholder").classList.remove("hidden"),fcn_ajaxGet({post_id:fcn_inlineStorage.postId,page:fcn_storyCommentPage},"get_story_comments").then((e=>{e.success?(_$(".fictioneer-comments__list > ul").innerHTML+=e.data.html,fcn_storyCommentPage++):e.data?.error&&(t=fcn_buildErrorNotice(e.data.error))})).catch((e=>{t=fcn_buildErrorNotice(e)})).then((()=>{_$(".comments-loading-placeholder").remove(),t&&_$(".fictioneer-comments__list > ul").appendChild(t)}))}fcn_applyStorySettings(),_$$$("button-toggle-chapter-order")?.addEventListener("click",(t=>{fcn_storySettings.order="asc"===t.currentTarget.dataset.order?"desc":"asc",fcn_setStorySettings(fcn_storySettings),fcn_applyStorySettings()})),_$$$("button-toggle-chapter-view")?.addEventListener("click",(t=>{fcn_storySettings.view="list"===t.currentTarget.dataset.view?"grid":"list",fcn_setStorySettings(fcn_storySettings),fcn_applyStorySettings()})),_$$(".chapter-group__folding-toggle").forEach((t=>{t.addEventListener("click",(t=>{const e=t.currentTarget.closest(".chapter-group[data-folded]");e&&(e.dataset.folded="true"==e.dataset.folded?"false":"true")}))})),_$$(".tabs__item").forEach((t=>{t.addEventListener("click",(t=>{fcn_toggleStoryTab(t.currentTarget)}))})),_$(".comment-section")?.addEventListener("click",(t=>{t.target?.classList.contains("load-more-comments-button")&&fcn_loadStoryComments()}));

2
js/utility.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -23,7 +23,7 @@ if (
$request_uri = parse_url( $_SERVER['REQUEST_URI'], PHP_URL_PATH );
// Check REST Request
if ( strpos( $request_uri, 'wp-json/fictioneer_rest/v1/fictioneer_' ) !== false ) {
if ( strpos( $request_uri, 'wp-json/fictioneer/' ) !== false ) {
add_filter( 'option_active_plugins', 'fictioneer_exclude_plugins' );
}

View File

@ -189,13 +189,15 @@ function fcn_loadStoryComments() {
_$('.load-more-list-item').remove();
_$('.comments-loading-placeholder').classList.remove('hidden');
// Request
fcn_ajaxGet({
'action': 'fictioneer_ajax_request_story_comments',
'post_id': fcn_inlineStorage.postId,
'page': fcn_storyCommentPage
})
.then((response) => {
// REST request
fcn_ajaxGet(
{
'post_id': fcn_inlineStorage.postId,
'page': fcn_storyCommentPage
},
'get_story_comments'
)
.then(response => {
// Check for success
if (response.success) {
_$('.fictioneer-comments__list > ul').innerHTML += response.data.html;
@ -204,7 +206,7 @@ function fcn_loadStoryComments() {
errorNote = fcn_buildErrorNotice(response.data.error);
}
})
.catch((error) => {
.catch(error => {
errorNote = fcn_buildErrorNotice(error);
})
.then(() => {
@ -212,7 +214,9 @@ function fcn_loadStoryComments() {
_$('.comments-loading-placeholder').remove();
// Add error if any
if (errorNote) _$('.fictioneer-comments__list > ul').appendChild(errorNote);
if (errorNote) {
_$('.fictioneer-comments__list > ul').appendChild(errorNote);
}
});
}

View File

@ -46,18 +46,23 @@ const _$$$ = document.getElementById.bind(document);
// =============================================================================
/**
* Make an AJAX POST request with the Fetch API.
* Make a POST request with the Fetch API
*
* @since 5.0
* @param {Object} data - The payload, including the action and nonce.
* @param {String} url - Optional. The AJAX URL if different from the default.
* @param {String} url - Optional. The request URL if different from the default.
* @param {Object} headers - Optional. Headers for the request.
* @returns {Promise} A Promise that resolves to the parsed JSON response if successful.
*/
async function fcn_ajaxPost(data = {}, url = null, headers = {}) {
// Auto-complete REST request if not a full URL
if (url && !url.startsWith('http')) {
url = fictioneer_ajax.rest_url + url;
}
// Get URL if not provided
if (!url) url = fictioneer_ajax.ajax_url;
url = url ? url : fictioneer_ajax.ajax_url;
// Default headers
final_headers = {
@ -89,16 +94,21 @@ async function fcn_ajaxPost(data = {}, url = null, headers = {}) {
}
/**
* Make an AJAX GET request with the Fetch API.
* Make a GET request with the Fetch API.
*
* @since 5.0
* @param {Object} data - The payload, including the action and nonce.
* @param {String} url - Optional. The AJAX URL if different from the default.
* @param {String} url - Optional. The request URL if different from the default.
* @param {Object} headers - Optional. Headers for the request.
* @return {JSON} The parsed JSON response if successful.
*/
async function fcn_ajaxGet(data = {}, url = null, headers = {}) {
// Auto-complete REST request if not a full URL
if (url && !url.startsWith('http')) {
url = fictioneer_ajax.rest_url + url;
}
// Build URL
url = url ? url : fictioneer_ajax.ajax_url;
data = {...{'nonce': fcn_getNonce()}, ...data};
@ -841,7 +851,7 @@ function fcn_html(...args) {
// * @returns {Promise<number>} Promise that resolves with the average response time in milliseconds.
// */
// async function benchmarkAjax(n = 1, data = {}, url = null, headers = {}) {
// async function fcn_benchmarkAjax(n = 1, data = {}, url = null, headers = {}) {
// let totalTime = 0;
// console.log(`Starting benchmark with ${n} AJAX requests...`);
@ -864,3 +874,15 @@ function fcn_html(...args) {
// return averageTime;
// }
// /**
// * Makes a GET request and prints the response to the console
// *
// * @param {Object} payload - The data payload to be sent with the AJAX request.
// *
// * @example fcn_ajaxPrintResponse({'action': 'the_function', 'fcn_fast_ajax': 1})
// */
// function fcn_printAjaxResponse(payload) {
// fcn_ajaxGet(payload).then((response) => { console.log(response); });
// }