/** * WP Post object. Only properties needed by the code are included. * * @typedef {object} WPPost * @property {number} id ID of post. * @property {number} author Post author ID. * @property {number[]} categories IDs of associated categories. * @property {number[]} tags IDs of associated tags. * @property {number} featured_media ID of featured image. */ /** * Get a collection of REST resources. * * @param {string} route Route to GET. * @param {object} [query] Query parameter map (optional). * @param {boolean} [retry] Whether to allow retry on failure (optional). * @returns {Promise} Promise to JSON results of query. */ const get = async ( route, query = {}, retry = true ) => { try { const result = await fetch( `/wp-json${ route }?${ ( new URLSearchParams( query ) ).toString() }` ); return result.json(); } catch ( e ) { if ( retry ) { // Retry once. return get( route, query, false ); } // Re-throw if failure. throw e; } }; /** * Register and then fetch multiple API resources. */ class Resource { /** * Construct the API Resource object. * * @param {string} route Collection endpoint for this API resource. * @param {object} query Query parameters to use when fetching. */ constructor( route, query = {} ) { this.route = route; this.query = query; // Dictionary of resources to fetch, and later, their values. this.resources = {}; } /** * Prepare to fetch one or more resources by ID. * * @param {number|number[]} resourceId One or more resource IDs. */ include( resourceId ) { if ( Array.isArray( resourceId ) ) { resourceId.forEach( ( id ) => { this.resources[ id ] = true; } ); } else { this.resources[ resourceId ] = true; } } /** * Set a resource value by ID. * * @param {number} id ID of resource. * @param {object} resource Resource object. */ set( id, resource ) { this.resources[ id ] = resource; } /** * Get one or more resources from the fetched data. * * @param {number|number[]} id ID of resource to return. * @returns {object|number|number[]} Resource object, or unchanged ID if resource not found. */ get( id ) { return this.resources[ id ] || id; } /** * Get multiple resources from the fetched data. * * @param {number[]} ids IDs of resource to return. * @returns {Array} Array of resources, or their IDs if not found. */ getMultiple( ids ) { return ids.map( ( id ) => this.get( id ) ); } /** * Fetch all registered IDs and store them in the resources dictionary. * * @async * @returns {Promise<Array>} Resolves to array of returned resources. */ async fetch() { const ids = Object.keys( this.resources ); const resources = await get( this.route, { ...this.query, include: ids.join(), per_page: ids.length, } ); resources.forEach( ( resource ) => { this.set( resource.id, resource ); } ); return resources; } } /** * Get recent posts with minimal unnecessary fetching. * * @returns {Promise<object[]>} Promise to array of recent posts, including embedded values. */ const getRecentPosts = async () => { /** @type {WPPost[]} */ let posts = []; // Create instances of our Resource class for each "embedded" resource. const authors = new Resource( '/wp/v2/users', { _fields: 'id,link,name,avatar_urls', } ); const media = new Resource( '/wp/v2/media', { _fields: 'id,media_details', } ); const tags = new Resource( '/wp/v2/tags', { _fields: 'id,name,link', } ); const categories = new Resource( '/wp/v2/categories', { _fields: 'id,name,link', } ); try { // Fetch the posts. posts = await get( '/wp/v2/posts', { _fields: 'id,author,categories,date_gmt,excerpt,featured_media,link,modified_gmt,tags,title', } ); // Then set up the Resource objects with the IDs of linked resources. posts.forEach( ( post ) => { authors.include( post.author ); media.include( post.featured_media ); tags.include( post.tags ); categories.include( post.categories ); } ); // Get all the "embedded" data in parallel. await Promise.all( [ authors.fetch(), tags.fetch(), categories.fetch(), media.fetch(), ] ); } catch ( e ) { console.error( e ); } return posts.map( ( post ) => ( { ...post, author: authors.get( post.author ), tags: tags.getMultiple( post.tags ), categories: categories.getMultiple( post.categories ), media: media.get( post.featured_media ), } ) ); };