Files
2meet-data-optimizer/admin/assets/wpdo-rest-sdk.js
T
wpdev d36bb954d1 chore: initial snapshot of 2meet-data-optimizer v0.1.0
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
2026-07-31 05:06:36 +08:00

248 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* WP Data Optimizer — REST API JavaScript SDK
*
* Lightweight client for /wp-json/wpdo/v1/ endpoints.
* No dependencies required.
*
* Usage:
* const wpdo = new WpdoClient();
*
* // Fetch listings (Zone A)
* const { items, total } = await wpdo.getListings({ hp_price_min: 100, per_page: 20 });
*
* // Single listing (Zone A + Zone C)
* const listing = await wpdo.getListing(123);
*
* // View count (Zone B)
* const { view_count } = await wpdo.getStats(123);
*
* // Increment view count (Zone B, requires WP REST nonce)
* const { view_count: updated } = await wpdo.incrementView(123);
*
* // Iterate all pages
* for await (const page of wpdo.paginateListings({ hp_featured: 1 })) {
* console.log(page.items);
* }
*
* @package WP_Data_Optimizer
*/
/* global wpdo_sdk_config */
( function ( global ) {
'use strict';
/**
* WpdoClient — REST API wrapper for WP Data Optimizer.
*
* @param {object} [options]
* @param {string} [options.baseUrl] REST base URL (default: auto-detected from wpdo_sdk_config or /wp-json)
* @param {string} [options.nonce] WP REST nonce for authenticated requests
* @param {string} [options.postType] Default post type (default: 'hp_listing')
*/
function WpdoClient( options ) {
options = options || {};
var config = ( typeof wpdo_sdk_config !== 'undefined' ) ? wpdo_sdk_config : {};
this._base = options.baseUrl || config.rest_url || '/wp-json/wpdo/v1';
this._nonce = options.nonce || config.nonce || '';
this._postType = options.postType || config.post_type || 'hp_listing';
}
// ── Core fetch ────────────────────────────────────────────────────────────
/**
* Internal GET fetch helper.
*
* @param {string} path Relative path (e.g. '/listings')
* @param {object} params Query parameters
* @return {Promise<{data: *, headers: Headers, status: number}>}
*/
WpdoClient.prototype._fetch = function ( path, params ) {
var url = this._base + path;
if ( params && Object.keys( params ).length ) {
var qs = Object.keys( params )
.filter(
function ( k ) {
return params[ k ] !== null && params[ k ] !== undefined && params[ k ] !== ''; }
)
.map(
function ( k ) {
return encodeURIComponent( k ) + '=' + encodeURIComponent( params[ k ] ); }
)
.join( '&' );
if ( qs ) {
url += '?' + qs;
}
}
var headers = { 'Content-Type': 'application/json' };
if ( this._nonce ) {
headers[ 'X-WP-Nonce' ] = this._nonce;
}
return fetch( url, { headers: headers } ).then(
function ( res ) {
return res.json().then(
function ( data ) {
return { data: data, headers: res.headers, status: res.status };
}
);
}
);
};
/**
* Internal POST fetch helper.
*
* @param {string} path Relative path
* @param {object} body JSON body (optional)
* @return {Promise<{data: *, status: number}>}
*/
WpdoClient.prototype._post = function ( path, body ) {
var headers = { 'Content-Type': 'application/json' };
if ( this._nonce ) {
headers[ 'X-WP-Nonce' ] = this._nonce;
}
return fetch(
this._base + path,
{
method: 'POST',
headers: headers,
body: body ? JSON.stringify( body ) : null,
}
).then(
function ( res ) {
return res.json().then(
function ( data ) {
return { data: data, status: res.status };
}
);
}
);
};
// ── Public API ────────────────────────────────────────────────────────────
/**
* Fetch a page of listings from Zone A.
*
* @param {object} [params]
* @param {string} [params.post_type] Default: configured post type
* @param {number} [params.per_page] 1100, default 20
* @param {number} [params.page] Default 1
* @param {string} [params.orderby] Column name, default 'post_id'
* @param {string} [params.order] 'ASC' | 'DESC', default 'DESC'
* @param {number} [params.*_min] Numeric range filter (e.g. hp_price_min)
* @param {number} [params.*_max] Numeric range filter (e.g. hp_price_max)
* @param {*} [params.*] Exact match filter (e.g. hp_featured: 1)
* @return {Promise<{items: Array, total: number, totalPages: number}>}
*/
WpdoClient.prototype.getListings = function ( params ) {
var merged = Object.assign( { post_type: this._postType }, params || {} );
return this._fetch( '/listings', merged ).then(
function ( res ) {
return {
items: res.data,
total: parseInt( res.headers.get( 'X-WP-Total' ) || '0', 10 ),
totalPages: parseInt( res.headers.get( 'X-WP-TotalPages' ) || '0', 10 ),
status: res.status,
};
}
);
};
/**
* Fetch a single listing (Zone A + Zone C merged).
*
* @param {number} id Post ID
* @return {Promise<object>}
*/
WpdoClient.prototype.getListing = function ( id ) {
return this._fetch( '/listings/' + id, null ).then(
function ( res ) {
return res.data;
}
);
};
/**
* Fetch Zone B view count for a post.
*
* @param {number} id Post ID
* @return {Promise<{post_id: number, view_count: number}>}
*/
WpdoClient.prototype.getStats = function ( id ) {
return this._fetch( '/stats/' + id, null ).then(
function ( res ) {
return res.data;
}
);
};
/**
* Increment Zone B view count for a post (requires WP REST nonce).
*
* The nonce must be passed via the `nonce` constructor option or
* via `wpdo_sdk_config.nonce` (wp_create_nonce('wp_rest')).
*
* @param {number} id Post ID
* @return {Promise<{post_id: number, view_count: number}>}
*
* Example:
* // Auto-tracks a view when a listing page loads:
* document.addEventListener('DOMContentLoaded', () => {
* const postId = parseInt(document.body.dataset.postId);
* if (postId) wpdo.incrementView(postId);
* });
*/
WpdoClient.prototype.incrementView = function ( id ) {
return this._post( '/listings/' + id + '/view', null ).then(
function ( res ) {
return res.data;
}
);
};
/**
* Async generator — iterates every page of listings.
*
* @param {object} [params] Same params as getListings (page is managed internally)
* @yields {{items: Array, total: number, page: number, totalPages: number}}
*
* Example:
* for await (const page of wpdo.paginateListings({ hp_featured: 1 })) {
* page.items.forEach(item => console.log(item));
* }
*/
WpdoClient.prototype.paginateListings = async function * ( params ) {
var page = 1;
var total = Infinity;
var perPage = ( params && params.per_page ) ? params.per_page : 20;
while ( ( page - 1 ) * perPage < total ) {
var merged = Object.assign( {}, params || {}, { page: page } );
var result = await this.getListings( merged );
total = result.total;
yield { items: result.items, total: total, page: page, totalPages: result.totalPages };
if ( page >= result.totalPages ) {
break;
}
page++;
}
};
// ── Export ────────────────────────────────────────────────────────────────
global.WpdoClient = WpdoClient;
// Auto-instantiate as window.wpdo if config is present.
if ( typeof wpdo_sdk_config !== 'undefined' ) {
global.wpdo = new WpdoClient();
}
}( window ) );