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
This commit is contained in:
2026-07-31 05:06:36 +08:00
commit d36bb954d1
206 changed files with 66538 additions and 0 deletions
+247
View File
@@ -0,0 +1,247 @@
/**
* 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 ) );