Refactor bookmarks

This commit is contained in:
Tetrakern 2023-08-27 01:54:46 +02:00
parent cb87988e8f
commit f33abf16c5
12 changed files with 207 additions and 201 deletions

View File

@ -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`, `fictioneer_flush_object_cache`
| `untrash_post` | `fictioneer_refresh_post_caches`, `fictioneer_track_chapter_and_story_updates`, `fictioneer_update_modified_date_on_story_for_chapter`, `fictioneer_purge_transients`, `fictioneer_flush_object_cache`
| `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_bookmarks`, `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_nonce`, `fictioneer_ajax_get_reminders_list`, `fictioneer_ajax_is_user_logged_in`, `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`
| `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_nonce`, `fictioneer_ajax_get_reminders_list`, `fictioneer_ajax_is_user_logged_in`, `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`
| `wp_ajax_nopriv_*` | `fictioneer_ajax_get_comment_form`, `fictioneer_ajax_get_comment_section`, `fictioneer_ajax_get_nonce`, `fictioneer_ajax_is_user_logged_in`, `fictioneer_ajax_submit_comment`
| `wp_before_admin_bar_render` | `fictioneer_remove_admin_bar_links`, `fictioneer_remove_dashboard_from_admin_bar`
| `wp_dashboard_setup` | `fictioneer_remove_dashboard_widgets`

View File

@ -13,7 +13,6 @@ if ( ! defined( 'FICTIONEER_FAST_AJAX_FUNCTIONS' ) ) {
'fictioneer_ajax_get_nonce',
// Bookmarks
'fictioneer_ajax_save_bookmarks',
'fictioneer_ajax_get_bookmarks',
// Follows
'fictioneer_ajax_toggle_follow',
'fictioneer_ajax_clear_my_follows',

View File

@ -1,48 +1,5 @@
<?php
// =============================================================================
// GET BOOKMARKS FOR USERS - AJAX
// =============================================================================
/**
* Get an user's bookmarks via AJAX
*
* @since Fictioneer 4.0
* @link https://developer.wordpress.org/reference/functions/wp_send_json_success/
* @link https://developer.wordpress.org/reference/functions/wp_send_json_error/
* @see fictioneer_get_validated_ajax_user()
*/
function fictioneer_ajax_get_bookmarks() {
// Enabled?
if ( ! get_option( 'fictioneer_enable_bookmarks' ) ) {
wp_send_json_error(
array( 'error' => __( 'Not allowed.', 'fictioneer' ) ),
403
);
}
// Setup and validations
$user = fictioneer_get_validated_ajax_user();
if ( ! $user ) {
wp_send_json_error( array( 'error' => __( 'Request did not pass validation.', 'fictioneer' ) ) );
}
// Look for saved bookmarks on user...
$bookmarks = get_user_meta( $user->ID, 'fictioneer_bookmarks', true );
$bookmarks = $bookmarks ? $bookmarks : '{}';
// Response
if ( $bookmarks ) {
wp_send_json_success( array( 'bookmarks' => $bookmarks ) );
}
}
if ( get_option( 'fictioneer_enable_bookmarks' ) ) {
add_action( 'wp_ajax_fictioneer_ajax_get_bookmarks', 'fictioneer_ajax_get_bookmarks' );
}
// =============================================================================
// SAVE BOOKMARKS FOR USERS - AJAX
// =============================================================================

File diff suppressed because one or more lines are too long

2
js/bookmarks.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
function fcn_unsetOauth(t,e,n){const a=prompt(sprintf(_x("Are you sure? Note that if you disconnect all accounts, you may no longer be able to log back in once you log out. Enter %s to confirm.","Unset OAuth prompt.","fictioneer"),_x("delete","Prompt deletion confirmation string.","fictioneer").toUpperCase()));if(!a||a.toLowerCase()!=_x("delete","Prompt deletion confirmation string.","fictioneer").toLowerCase())return;const r=_$$$(`oauth-${e}`);r.classList.add("ajax-in-progress"),fcn_ajaxPost(payload={action:"fictioneer_ajax_unset_my_oauth",nonce:t,channel:e,id:n}).then((t=>{t.success?(r.classList.remove("_connected"),r.classList.add("_disconnected"),r.querySelector("button").remove(),fcn_showNotification(r.dataset.unset)):(r.style.background="var(--warning)",fcn_showNotification(t.data.error,5,"warning"))})).catch((t=>{t.status&&t.statusText&&(r.style.background="var(--warning)",fcn_showNotification(`${t.status}: ${t.statusText}`,5,"warning"))})).then((()=>{r.classList.remove("ajax-in-progress")}))}function fcn_deleteMyAccount(t){if(_$$$("button-delete-my-account").hasAttribute("disabled"))return;const e=prompt(sprintf(t.dataset.warning,_x("delete","Prompt deletion confirmation string.","fictioneer").toUpperCase()));e&&e.toLowerCase()==_x("delete","Prompt deletion confirmation string.","fictioneer").toLowerCase()&&(_$$$("button-delete-my-account").setAttribute("disabled",!0),fcn_ajaxPost({action:"fictioneer_ajax_delete_my_account",nonce:t.dataset.nonce,id:t.dataset.id}).then((t=>{t.success?location.reload():(fcn_showNotification(t.data.error,5,"warning"),_$$$("button-delete-my-account").innerHTML=t.data.button)})).catch((t=>{t.status&&t.statusText&&(fcn_showNotification(`${t.status}: ${t.statusText}`,5,"warning"),_$$$("button-delete-my-account").innerHTML=response.data.button)})))}_$$(".button-unset-oauth").forEach((t=>{t.addEventListener("click",(t=>{fcn_unsetOauth(t.currentTarget.dataset.nonce,t.currentTarget.dataset.channel,t.currentTarget.dataset.id)}))})),_$$$("button-delete-my-account")?.addEventListener("click",(t=>{fcn_deleteMyAccount(t.currentTarget)}));const fcn_profileDataTranslations=_$$$("profile-data-translations")?.dataset;function fcn_dataDeletionPrompt(t){const e=prompt(sprintf(t.dataset.warning,_x("delete","Prompt deletion confirmation string.","fictioneer").toUpperCase()));return!(!e||e.toLowerCase()!=_x("delete","Prompt deletion confirmation string.","fictioneer").toLowerCase())}function fcn_clearData(t,e){const n=t.closest(".card");localStorage.removeItem("fcnBookshelfContent"),n.classList.add("ajax-in-progress"),t.remove(),fcn_ajaxPost({action:e,fcn_fast_ajax:1,nonce:t.dataset.nonce}).then((t=>{t.success?n.querySelector(".card__content").innerHTML=t.data.success:fcn_showNotification(t.data.error,10,"warning")})).catch((t=>{t.status&&t.statusText&&fcn_showNotification(`${t.status}: ${t.statusText}`,10,"warning")})).then((()=>{n.classList.remove("ajax-in-progress")}))}_$(".button-clear-comments")?.addEventListener("click",(t=>{fcn_dataDeletionPrompt(t.currentTarget)&&fcn_clearData(t.currentTarget,"fictioneer_ajax_clear_my_comments")})),_$(".button-clear-comment-subscriptions")?.addEventListener("click",(t=>{fcn_dataDeletionPrompt(t.currentTarget)&&fcn_clearData(t.currentTarget,"fictioneer_ajax_clear_my_comment_subscriptions")})),_$(".button-clear-checkmarks")?.addEventListener("click",(t=>{if(!fcn_dataDeletionPrompt(t.currentTarget))return;const e=fcn_getUserData();e.checkmarks={data:{},updated:Date.now()},fcn_setUserData(e),fcn_updateCheckmarksView(),fcn_clearData(t.currentTarget,"fictioneer_ajax_clear_my_checkmarks",!0)})),_$(".button-clear-reminders")?.addEventListener("click",(t=>{if(!fcn_dataDeletionPrompt(t.currentTarget))return;const e=fcn_getUserData();e.reminders={data:{}},fcn_setUserData(e),fcn_updateRemindersView(),fcn_clearData(t.currentTarget,"fictioneer_ajax_clear_my_reminders",!0)})),_$(".button-clear-follows")?.addEventListener("click",(t=>{if(!fcn_dataDeletionPrompt(t.currentTarget))return;const e=fcn_getUserData();e.follows={data:{}},fcn_setUserData(e),fcn_updateFollowsView(),fcn_clearData(t.currentTarget,"fictioneer_ajax_clear_my_follows",!0)})),_$(".button-clear-bookmarks")?.addEventListener("click",(t=>{fcn_dataDeletionPrompt(t.currentTarget)&&(fcn_bookmarks.data&&Object.keys(fcn_bookmarks.data).forEach((t=>fcn_removeBookmark(t))),t.currentTarget.closest(".card").querySelector(".card__content").innerHTML=fcn_profileDataTranslations.clearedSuccess,fcn_setBookmarks(fcn_bookmarks))}));
function fcn_unsetOauth(t,e,n){const a=prompt(sprintf(_x("Are you sure? Note that if you disconnect all accounts, you may no longer be able to log back in once you log out. Enter %s to confirm.","Unset OAuth prompt.","fictioneer"),_x("delete","Prompt deletion confirmation string.","fictioneer").toUpperCase()));if(!a||a.toLowerCase()!=_x("delete","Prompt deletion confirmation string.","fictioneer").toLowerCase())return;const r=_$$$(`oauth-${e}`);r.classList.add("ajax-in-progress"),fcn_ajaxPost(payload={action:"fictioneer_ajax_unset_my_oauth",nonce:t,channel:e,id:n}).then((t=>{t.success?(r.classList.remove("_connected"),r.classList.add("_disconnected"),r.querySelector("button").remove(),fcn_showNotification(r.dataset.unset)):(r.style.background="var(--warning)",fcn_showNotification(t.data.error,5,"warning"))})).catch((t=>{t.status&&t.statusText&&(r.style.background="var(--warning)",fcn_showNotification(`${t.status}: ${t.statusText}`,5,"warning"))})).then((()=>{r.classList.remove("ajax-in-progress")}))}function fcn_deleteMyAccount(t){if(_$$$("button-delete-my-account").hasAttribute("disabled"))return;const e=prompt(sprintf(t.dataset.warning,_x("delete","Prompt deletion confirmation string.","fictioneer").toUpperCase()));e&&e.toLowerCase()==_x("delete","Prompt deletion confirmation string.","fictioneer").toLowerCase()&&(_$$$("button-delete-my-account").setAttribute("disabled",!0),fcn_ajaxPost({action:"fictioneer_ajax_delete_my_account",nonce:t.dataset.nonce,id:t.dataset.id}).then((t=>{t.success?location.reload():(fcn_showNotification(t.data.error,5,"warning"),_$$$("button-delete-my-account").innerHTML=t.data.button)})).catch((t=>{t.status&&t.statusText&&(fcn_showNotification(`${t.status}: ${t.statusText}`,5,"warning"),_$$$("button-delete-my-account").innerHTML=response.data.button)})))}_$$(".button-unset-oauth").forEach((t=>{t.addEventListener("click",(t=>{fcn_unsetOauth(t.currentTarget.dataset.nonce,t.currentTarget.dataset.channel,t.currentTarget.dataset.id)}))})),_$$$("button-delete-my-account")?.addEventListener("click",(t=>{fcn_deleteMyAccount(t.currentTarget)}));const fcn_profileDataTranslations=_$$$("profile-data-translations")?.dataset;function fcn_dataDeletionPrompt(t){const e=prompt(sprintf(t.dataset.warning,_x("delete","Prompt deletion confirmation string.","fictioneer").toUpperCase()));return!(!e||e.toLowerCase()!=_x("delete","Prompt deletion confirmation string.","fictioneer").toLowerCase())}function fcn_clearData(t,e){const n=t.closest(".card");localStorage.removeItem("fcnBookshelfContent"),n.classList.add("ajax-in-progress"),t.remove(),fcn_ajaxPost({action:e,fcn_fast_ajax:1,nonce:t.dataset.nonce}).then((t=>{t.success?n.querySelector(".card__content").innerHTML=t.data.success:fcn_showNotification(t.data.error,10,"warning")})).catch((t=>{t.status&&t.statusText&&fcn_showNotification(`${t.status}: ${t.statusText}`,10,"warning")})).then((()=>{n.classList.remove("ajax-in-progress")}))}_$(".button-clear-comments")?.addEventListener("click",(t=>{fcn_dataDeletionPrompt(t.currentTarget)&&fcn_clearData(t.currentTarget,"fictioneer_ajax_clear_my_comments")})),_$(".button-clear-comment-subscriptions")?.addEventListener("click",(t=>{fcn_dataDeletionPrompt(t.currentTarget)&&fcn_clearData(t.currentTarget,"fictioneer_ajax_clear_my_comment_subscriptions")})),_$(".button-clear-checkmarks")?.addEventListener("click",(t=>{if(!fcn_dataDeletionPrompt(t.currentTarget))return;const e=fcn_getUserData();e.checkmarks={data:{},updated:Date.now()},fcn_setUserData(e),fcn_updateCheckmarksView(),fcn_clearData(t.currentTarget,"fictioneer_ajax_clear_my_checkmarks",!0)})),_$(".button-clear-reminders")?.addEventListener("click",(t=>{if(!fcn_dataDeletionPrompt(t.currentTarget))return;const e=fcn_getUserData();e.reminders={data:{}},fcn_setUserData(e),fcn_updateRemindersView(),fcn_clearData(t.currentTarget,"fictioneer_ajax_clear_my_reminders",!0)})),_$(".button-clear-follows")?.addEventListener("click",(t=>{if(!fcn_dataDeletionPrompt(t.currentTarget))return;const e=fcn_getUserData();e.follows={data:{}},fcn_setUserData(e),fcn_updateFollowsView(),fcn_clearData(t.currentTarget,"fictioneer_ajax_clear_my_follows",!0)})),_$(".button-clear-bookmarks")?.addEventListener("click",(t=>{if(!fcn_dataDeletionPrompt(t.currentTarget))return;const e=fcn_getUserData();e.bookmarks="{}",fcn_setUserData(e),fcn_bookmarks.data={},t.currentTarget.closest(".card").querySelector(".card__content").innerHTML=fcn_profileDataTranslations.clearedSuccess,fcn_setBookmarks(fcn_bookmarks)}));

View File

@ -604,4 +604,48 @@ function fictioneer_ajax_get_fingerprint() {
}
add_action( 'wp_ajax_fictioneer_ajax_get_fingerprint', 'fictioneer_ajax_get_fingerprint' );
// =============================================================================
// GET BOOKMARKS FOR USERS - AJAX
// =============================================================================
/**
* Get an user's bookmarks via AJAX
*
* @since Fictioneer 4.0
* @link https://developer.wordpress.org/reference/functions/wp_send_json_success/
* @link https://developer.wordpress.org/reference/functions/wp_send_json_error/
* @see fictioneer_get_validated_ajax_user()
*/
function fictioneer_ajax_get_bookmarks() {
// Enabled?
if ( ! get_option( 'fictioneer_enable_bookmarks' ) ) {
wp_send_json_error(
array( 'error' => __( 'Not allowed.', 'fictioneer' ) ),
403
);
}
// Setup and validations
$user = fictioneer_get_validated_ajax_user();
if ( ! $user ) {
wp_send_json_error( array( 'error' => __( 'Request did not pass validation.', 'fictioneer' ) ) );
}
// Look for saved bookmarks on user...
$bookmarks = get_user_meta( $user->ID, 'fictioneer_bookmarks', true );
$bookmarks = $bookmarks ? $bookmarks : '{}';
// Response
if ( $bookmarks ) {
wp_send_json_success( array( 'bookmarks' => $bookmarks ) );
}
}
if ( get_option( 'fictioneer_enable_bookmarks' ) ) {
add_action( 'wp_ajax_fictioneer_ajax_get_bookmarks', 'fictioneer_ajax_get_bookmarks' );
}
?>

View File

@ -297,8 +297,6 @@ function fcn_setLoggedInState(state, initialize = true) {
// Setup local user data, but only if the login state has not been added
// synchronous from local storage (avoid double initialize).
if (initialize) {
if (typeof fcn_getBookmarksForUser === 'function') fcn_getBookmarksForUser();
if (typeof fcn_showChapterBookmark === 'function') fcn_showChapterBookmark();
fcn_getProfileImage();
}
}

View File

@ -9,126 +9,54 @@ const /** @const {HTMLElement[]} */ fcn_jumpToBookmarkButtons = _$$('.button--bo
/** @const {HTMLElement} */ fcn_bookmarksSmallCardBlock = _$('.bookmarks-block'),
/** @const {HTMLElement} */ fcn_bookmarksSmallCardTemplate = _$('.bookmark-small-card-template');
var /** @type {Object} */ fcn_bookmarks = fcn_getBookmarks(),
var /** @type {Object} */ fcn_bookmarks,
/** @type {Number} */ fcn_userBookmarksTimeout;
// Initialize
fcn_getBookmarksForUser();
fcn_showChapterBookmark();
fcn_initializeLocalBookmarks();
document.addEventListener('fcnUserDataReady', event => {
fcn_initializeUserBookmarks(event);
});
// =============================================================================
// SAVE BOOKMARKS FOR USER
// INITIALIZE
// =============================================================================
/**
* Save bookmarks JSON to the database via AJAX.
* Initialize local bookmarks.
*
* @description Saves the bookmarks JSON to the database if an user is logged in,
* otherwise the function aborts to avoid unnecessary requests. This can only
* happen once every n seconds, as set by the timeout interval, to avoid further
* unnecessary requests in case someone triggers multiple updates.
* @description Looks for bookmarks in local storage, both for guests and logged-in
* users. If none are found, an empty bookmarks JSON is created and set.
*
* @since 4.0
* @param {String} value - The stringified bookmarks JSON to be saved.
* @see fcn_isValidJSONString()
*/
function fcn_saveBookmarksForUser(value) {
// Do not proceed if not logged in
if (!fcn_isLoggedIn) return;
function fcn_initializeLocalBookmarks() {
// Look for bookmarks in local storage
fcn_bookmarks = fcn_getBookmarks();
// Reset AJAX threshold
fcn_bookmarks['lastLoaded'] = 0;
// Always update bookmarks in storage
fcn_setBookmarks(fcn_bookmarks, true);
// Save to local storage
localStorage.setItem('fcnChapterBookmarks', JSON.stringify(fcn_bookmarks));
// Payload
const payload = {
'action': 'fictioneer_ajax_save_bookmarks',
'fcn_fast_ajax': 1,
'bookmarks': value
}
// Clear previous timeout (if still pending)
clearTimeout(fcn_userBookmarksTimeout);
// Only one save request every n seconds
fcn_userBookmarksTimeout = setTimeout(() => {
fcn_ajaxPost(payload)
.then((response) => {
// Check for failure
if (response.data.error) {
fcn_showNotification(response.data.error, 3, 'warning');
}
})
.catch((error) => {
if (error.status && error.statusText) {
fcn_showNotification(`${error.status}: ${error.statusText}`, 3, 'warning');
}
});
}, fictioneer_ajax.post_debounce_rate); // Debounce synchronization
// Update view
fcn_updateBookmarksView();
}
// =============================================================================
// GET BOOKMARKS FOR USER
// =============================================================================
/**
* Fetch bookmarks JSON from database via AJAX and set it.
* Initialize Bookmarks for logged-in users.
*
* @description Check whether to pull bookmarks for a logged-in user from the
* database. This can only happen once every n seconds, as set by the difference
* between the fcn_ajaxLimitThreshold and 'lastLoaded' node, in order to reduce
* the number of requests if someone is just browsing.
*
* @since 4.0
* @see fcn_setBookmarks()
* @see fcn_showChapterBookmark()
* @see fcn_updateBookmarkCards()
* @since 5.7.0
* @param {Event} event - The fcnUserDataReady event.
*/
function fcn_getBookmarksForUser() {
// Do not proceed if not logged in
if (!fcn_isLoggedIn) {
return;
}
function fcn_initializeUserBookmarks(event) {
// Always update bookmarks in storage
fcn_setBookmarks(JSON.parse(event.detail.data.bookmarks), true);
// Only update from server after some time has passed (e.g. 60 seconds)
if (fcn_ajaxLimitThreshold < fcn_bookmarks['lastLoaded']) {
if (Object.keys(fcn_bookmarks.data).length > 0) fcn_updateBookmarkCards();
return;
}
// Request
fcn_ajaxGet({
'action': 'fictioneer_ajax_get_bookmarks',
'fcn_fast_ajax': 1
})
.then((response) => {
// Check for success
if (response.success) {
// Unpack
let bookmarks = response.data.bookmarks;
// Validate
bookmarks = fcn_isValidJSONString(bookmarks) ? bookmarks : '{}';
bookmarks = JSON.parse(bookmarks);
if (Object.keys(bookmarks).length > 2) {
bookmarks = {};
}
// Setup
if (typeof bookmarks === 'object' && bookmarks.data) {
bookmarks['lastLoaded'] = Date.now();
fcn_setBookmarks(bookmarks, true);
fcn_showChapterBookmark();
}
}
// Regardless of success
fcn_updateBookmarkCards();
});
// Update view
fcn_updateBookmarksView();
}
// =============================================================================
@ -136,46 +64,20 @@ function fcn_getBookmarksForUser() {
// =============================================================================
/**
* Get bookmarks JSON from local storage or create new one.
* Initialize bookmarks from local storage.
*
* @description Looks for bookmarks in local storage, both for guests and logged-in
* users. If none are found, an empty bookmarks JSON is created, set, and returned.
*
* @since 4.0
* @since 5.7.0
* @see fcn_isValidJSONString()
* @return {Object} The bookmarks JSON.
*/
function fcn_getBookmarks() {
// Look for bookmarks in local storage
let b = localStorage.getItem('fcnChapterBookmarks');
fcn_bookmarks = localStorage.getItem('fcnChapterBookmarks');
// Check for valid JSON and create new one if not found
b = (b && fcn_isValidJSONString(b)) ? JSON.parse(b) : { 'lastLoaded': 0, 'data': {} };
// Always update bookmarks in storage
fcn_setBookmarks(b, true);
// Render bookmark cards if no AJAX request following
if (!fcn_isLoggedIn) {
fcn_updateBookmarkCards();
}
// Insert bookmarks count on user profile page
const stats = _$('.profile-bookmarks-stats');
if (stats) {
stats.innerHTML = stats.innerHTML.replace('%s', Object.keys(b.data).length);
}
if (Object.keys(b.data).length > 0) {
_$$('.icon-menu-bookmarks').forEach(element => {
element.classList.remove('hidden');
});
}
// Return bookmarks JSON
return b;
return (fcn_bookmarks && fcn_isValidJSONString(fcn_bookmarks)) ?
JSON.parse(fcn_bookmarks) : { 'data': {} };
}
// =============================================================================
@ -192,7 +94,7 @@ function fcn_getBookmarks() {
* unfinished requests can be cancelled without loss of data.
*
* @since 4.0
* @see fcn_saveBookmarksForUser()
* @see fcn_saveUserBookmarks()
* @param {Object} value - The bookmarks JSON to be set.
* @param {Boolean} [silent=false] - Whether or not to update the database.
*/
@ -205,16 +107,110 @@ function fcn_setBookmarks(value, silent = false) {
// Keep global updated
fcn_bookmarks = value;
// Save to local storage
localStorage.setItem('fcnChapterBookmarks', JSON.stringify(value));
// Do not save to database if silent update
if (silent) return;
// Keep user data updated as well
if (fcn_isLoggedIn) {
const currentUserData = fcn_getUserData();
// Update database for user; cancel any enqueued update via the timeout
window.clearTimeout(fcn_userBookmarksTimeout);
fcn_saveBookmarksForUser(JSON.stringify(value));
if (currentUserData) {
currentUserData.bookmarks = JSON.stringify(value);
fcn_setUserData(currentUserData);
}
}
// Do not save to database if silent update
if (silent) {
return;
}
// Update database for user
fcn_saveUserBookmarks(JSON.stringify(value));
}
// =============================================================================
// UPDATE BOOKMARKS VIEW
// =============================================================================
/**
* Updates the view with the current Bookmarks state.
*
* @since 5.7.0
*/
function fcn_updateBookmarksView() {
// Abort if bookmarks are not set
if (!fcn_bookmarks) {
return;
}
// Setup
const stats = _$('.profile-bookmarks-stats'),
count = Object.keys(fcn_bookmarks.data).length;
// Insert bookmarks count on user profile page
if (stats) {
stats.innerHTML = stats.innerHTML.replace('%s', count);
}
// Bookmark icons
if (count > 0) {
_$$('.icon-menu-bookmarks').forEach(element => {
element.classList.remove('hidden');
});
}
// Render bookmark cards if already logged-in
fcn_showBookmarkCards();
// Chapter bookmark
fcn_showChapterBookmark();
}
// =============================================================================
// SAVE USER BOOKMARKS
// =============================================================================
/**
* Save bookmarks JSON to the database via AJAX.
*
* @description Saves the bookmarks JSON to the database if an user is logged in,
* otherwise the function aborts to avoid unnecessary requests. This can only
* happen once every n seconds, as set by the timeout interval, to avoid further
* unnecessary requests in case someone triggers multiple updates.
*
* @since 4.0
* @param {String} value - The stringified bookmarks JSON to be saved.
*/
function fcn_saveUserBookmarks(value) {
// Do not proceed if not logged in
if (!fcn_isLoggedIn) {
return;
}
// Clear previous timeout (if still pending)
clearTimeout(fcn_userBookmarksTimeout);
// Only one save request every n seconds
fcn_userBookmarksTimeout = setTimeout(() => {
fcn_ajaxPost({
'action': 'fictioneer_ajax_save_bookmarks',
'fcn_fast_ajax': 1,
'bookmarks': value
})
.then(response => {
// Check for failure
if (response.data.error) {
fcn_showNotification(response.data.error, 3, 'warning');
}
})
.catch(error => {
if (error.status && error.statusText) {
fcn_showNotification(`${error.status}: ${error.statusText}`, 3, 'warning');
}
});
}, fictioneer_ajax.post_debounce_rate); // Debounce synchronization
}
// =============================================================================
@ -428,14 +424,17 @@ function fcn_setMobileMenuBookmarks() {
* @see fcn_bookmarkDeleteHandler()
*/
function fcn_updateBookmarkCards() {
// Check whether there are bookmark cards to render
function fcn_showBookmarkCards() {
// Check whether bookmark cards need to be rendered
if (
!fcn_bookmarks ||
!fcn_bookmarksSmallCardBlock ||
!fcn_bookmarksSmallCardTemplate ||
Object.keys(fcn_bookmarks.data).length < 1
) return;
Object.keys(fcn_bookmarks.data).length < 1 ||
_$('.bookmark-card')
) {
return;
}
// Make block visible
fcn_bookmarksSmallCardBlock.classList.remove('hidden');

View File

@ -122,8 +122,7 @@ function fcn_toggleFollow(storyId) {
// =============================================================================
/**
* Updates the view to reflect the current Follows state and saves the JSON
* to local storage.
* Updates the view with the current Follows state.
*
* @since 4.3
*/

View File

@ -118,8 +118,7 @@ function fcn_toggleReminder(storyId) {
// =============================================================================
/**
* Updates the view to reflect the current Reminders state and saves the JSON
* to local storage.
* Updates the view with the current Reminders state.
*
* @since 5.0
*/

View File

@ -274,6 +274,8 @@ _$('.button-clear-checkmarks')?.addEventListener(
const currentUserData = fcn_getUserData();
currentUserData.checkmarks = { 'data': {}, 'updated': Date.now() };
fcn_setUserData(currentUserData);
// Update views
fcn_updateCheckmarksView();
// Clear data
@ -298,6 +300,8 @@ _$('.button-clear-reminders')?.addEventListener(
const currentUserData = fcn_getUserData();
currentUserData.reminders = { 'data': {} };
fcn_setUserData(currentUserData);
// Update view
fcn_updateRemindersView();
// Clear data
@ -322,6 +326,8 @@ _$('.button-clear-follows')?.addEventListener(
const currentUserData = fcn_getUserData();
currentUserData.follows = { 'data': {} };
fcn_setUserData(currentUserData);
// Update view
fcn_updateFollowsView();
// Clear data
@ -338,15 +344,20 @@ _$('.button-clear-bookmarks')?.addEventListener(
'click',
e => {
// Confirm clear request using localized string
if (!fcn_dataDeletionPrompt(e.currentTarget)) return;
// Remove bookmarks
if (fcn_bookmarks.data) {
Object.keys(fcn_bookmarks.data).forEach(id => fcn_removeBookmark(id));
if (!fcn_dataDeletionPrompt(e.currentTarget)) {
return;
}
// Update database and view
// Remove bookmarks
const currentUserData = fcn_getUserData();
currentUserData.bookmarks = '{}';
fcn_setUserData(currentUserData);
fcn_bookmarks.data = {};
// Update view
e.currentTarget.closest('.card').querySelector('.card__content').innerHTML = fcn_profileDataTranslations.clearedSuccess;
// Update local storage and database
fcn_setBookmarks(fcn_bookmarks);
}
);