@wordpress/core-data
Version:
Access to and manipulation of core WordPress entities.
8 lines (7 loc) • 12 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../src/fetch/__experimental-fetch-link-suggestions.ts"],
"sourcesContent": ["/**\n * WordPress dependencies\n */\nimport apiFetch from '@wordpress/api-fetch';\nimport { addQueryArgs } from '@wordpress/url';\nimport { decodeEntities } from '@wordpress/html-entities';\nimport { __ } from '@wordpress/i18n';\n\nexport type SearchOptions = {\n\t/**\n\t * Displays initial search suggestions, when true.\n\t */\n\tisInitialSuggestions?: boolean;\n\t/**\n\t * Search options for initial suggestions.\n\t */\n\tinitialSuggestionsSearchOptions?: Omit<\n\t\tSearchOptions,\n\t\t'isInitialSuggestions' | 'initialSuggestionsSearchOptions'\n\t>;\n\t/**\n\t * Filters by search type.\n\t */\n\ttype?: 'attachment' | 'post' | 'term' | 'post-format';\n\t/**\n\t * Slug of the post-type or taxonomy.\n\t */\n\tsubtype?: string;\n\t/**\n\t * Which page of results to return.\n\t */\n\tpage?: number;\n\t/**\n\t * Search results per page.\n\t */\n\tperPage?: number;\n};\n\nexport type EditorSettings = {\n\t/**\n\t * Disables post formats, when true.\n\t */\n\tdisablePostFormats?: boolean;\n};\n\ntype SearchAPIResult = {\n\tid: number;\n\ttitle: string;\n\turl: string;\n\ttype: string;\n\tsubtype: string;\n};\n\ntype MediaAPIResult = {\n\tid: number;\n\ttitle: { rendered: string };\n\tsource_url: string;\n\ttype: string;\n};\n\nexport type SearchResult = {\n\t/**\n\t * Post or term id.\n\t */\n\tid: number;\n\t/**\n\t * Link url.\n\t */\n\turl: string;\n\t/**\n\t * Title of the link.\n\t */\n\ttitle: string;\n\t/**\n\t * The taxonomy or post type slug or type URL.\n\t */\n\ttype: string;\n\t/**\n\t * Link kind of post-type or taxonomy\n\t */\n\tkind?: string;\n};\n\n/**\n * Fetches link suggestions from the WordPress API.\n *\n * WordPress does not support searching multiple tables at once, e.g. posts and terms, so we\n * perform multiple queries at the same time and then merge the results together.\n *\n * @param search\n * @param searchOptions\n * @param editorSettings\n *\n * @example\n * ```js\n * import { __experimentalFetchLinkSuggestions as fetchLinkSuggestions } from '@wordpress/core-data';\n *\n * //...\n *\n * export function initialize( id, settings ) {\n *\n * settings.__experimentalFetchLinkSuggestions = (\n * search,\n * searchOptions\n * ) => fetchLinkSuggestions( search, searchOptions, settings );\n * ```\n */\nexport default async function fetchLinkSuggestions(\n\tsearch: string,\n\tsearchOptions: SearchOptions = {},\n\teditorSettings: EditorSettings = {}\n): Promise< SearchResult[] > {\n\tconst searchOptionsToUse =\n\t\tsearchOptions.isInitialSuggestions &&\n\t\tsearchOptions.initialSuggestionsSearchOptions\n\t\t\t? {\n\t\t\t\t\t...searchOptions,\n\t\t\t\t\t...searchOptions.initialSuggestionsSearchOptions,\n\t\t\t }\n\t\t\t: searchOptions;\n\n\tconst {\n\t\ttype,\n\t\tsubtype,\n\t\tpage,\n\t\tperPage = searchOptions.isInitialSuggestions ? 3 : 20,\n\t} = searchOptionsToUse;\n\n\tconst { disablePostFormats = false } = editorSettings;\n\n\tconst queries: Promise< SearchResult[] >[] = [];\n\n\tif ( ! type || type === 'post' ) {\n\t\tqueries.push(\n\t\t\tapiFetch< SearchAPIResult[] >( {\n\t\t\t\tpath: addQueryArgs( '/wp/v2/search', {\n\t\t\t\t\tsearch,\n\t\t\t\t\tpage,\n\t\t\t\t\tper_page: perPage,\n\t\t\t\t\ttype: 'post',\n\t\t\t\t\tsubtype,\n\t\t\t\t} ),\n\t\t\t} )\n\t\t\t\t.then( ( results ) => {\n\t\t\t\t\treturn results.map( ( result ) => {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: result.id,\n\t\t\t\t\t\t\turl: result.url,\n\t\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t\tdecodeEntities( result.title || '' ) ||\n\t\t\t\t\t\t\t\t__( '(no title)' ),\n\t\t\t\t\t\t\ttype: result.subtype || result.type,\n\t\t\t\t\t\t\tkind: 'post-type',\n\t\t\t\t\t\t};\n\t\t\t\t\t} );\n\t\t\t\t} )\n\t\t\t\t.catch( () => [] ) // Fail by returning no results.\n\t\t);\n\t}\n\n\tif ( ! type || type === 'term' ) {\n\t\tqueries.push(\n\t\t\tapiFetch< SearchAPIResult[] >( {\n\t\t\t\tpath: addQueryArgs( '/wp/v2/search', {\n\t\t\t\t\tsearch,\n\t\t\t\t\tpage,\n\t\t\t\t\tper_page: perPage,\n\t\t\t\t\ttype: 'term',\n\t\t\t\t\tsubtype,\n\t\t\t\t} ),\n\t\t\t} )\n\t\t\t\t.then( ( results ) => {\n\t\t\t\t\treturn results.map( ( result ) => {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: result.id,\n\t\t\t\t\t\t\turl: result.url,\n\t\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t\tdecodeEntities( result.title || '' ) ||\n\t\t\t\t\t\t\t\t__( '(no title)' ),\n\t\t\t\t\t\t\ttype: result.subtype || result.type,\n\t\t\t\t\t\t\tkind: 'taxonomy',\n\t\t\t\t\t\t};\n\t\t\t\t\t} );\n\t\t\t\t} )\n\t\t\t\t.catch( () => [] ) // Fail by returning no results.\n\t\t);\n\t}\n\n\tif ( ! disablePostFormats && ( ! type || type === 'post-format' ) ) {\n\t\tqueries.push(\n\t\t\tapiFetch< SearchAPIResult[] >( {\n\t\t\t\tpath: addQueryArgs( '/wp/v2/search', {\n\t\t\t\t\tsearch,\n\t\t\t\t\tpage,\n\t\t\t\t\tper_page: perPage,\n\t\t\t\t\ttype: 'post-format',\n\t\t\t\t\tsubtype,\n\t\t\t\t} ),\n\t\t\t} )\n\t\t\t\t.then( ( results ) => {\n\t\t\t\t\treturn results.map( ( result ) => {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: result.id,\n\t\t\t\t\t\t\turl: result.url,\n\t\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t\tdecodeEntities( result.title || '' ) ||\n\t\t\t\t\t\t\t\t__( '(no title)' ),\n\t\t\t\t\t\t\ttype: result.subtype || result.type,\n\t\t\t\t\t\t\tkind: 'taxonomy',\n\t\t\t\t\t\t};\n\t\t\t\t\t} );\n\t\t\t\t} )\n\t\t\t\t.catch( () => [] ) // Fail by returning no results.\n\t\t);\n\t}\n\n\tif ( ! type || type === 'attachment' ) {\n\t\tqueries.push(\n\t\t\tapiFetch< MediaAPIResult[] >( {\n\t\t\t\tpath: addQueryArgs( '/wp/v2/media', {\n\t\t\t\t\tsearch,\n\t\t\t\t\tpage,\n\t\t\t\t\tper_page: perPage,\n\t\t\t\t} ),\n\t\t\t} )\n\t\t\t\t.then( ( results ) => {\n\t\t\t\t\treturn results.map( ( result ) => {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tid: result.id,\n\t\t\t\t\t\t\turl: result.source_url,\n\t\t\t\t\t\t\ttitle:\n\t\t\t\t\t\t\t\tdecodeEntities( result.title.rendered || '' ) ||\n\t\t\t\t\t\t\t\t__( '(no title)' ),\n\t\t\t\t\t\t\ttype: result.type,\n\t\t\t\t\t\t\tkind: 'media',\n\t\t\t\t\t\t};\n\t\t\t\t\t} );\n\t\t\t\t} )\n\t\t\t\t.catch( () => [] ) // Fail by returning no results.\n\t\t);\n\t}\n\n\tconst responses = await Promise.all( queries );\n\n\tlet results = responses.flat();\n\tresults = results.filter( ( result ) => !! result.id );\n\tresults = sortResults( results, search );\n\tresults = results.slice( 0, perPage );\n\treturn results;\n}\n\n/**\n * Sort search results by relevance to the given query.\n *\n * Sorting is necessary as we're querying multiple endpoints and merging the results. For example\n * a taxonomy title might be more relevant than a post title, but by default taxonomy results will\n * be ordered after all the (potentially irrelevant) post results.\n *\n * We sort by scoring each result, where the score is the number of tokens in the title that are\n * also in the search query, divided by the total number of tokens in the title. This gives us a\n * score between 0 and 1, where 1 is a perfect match.\n *\n * @param results\n * @param search\n */\nexport function sortResults( results: SearchResult[], search: string ) {\n\tconst searchTokens = tokenize( search );\n\n\tconst scores = {};\n\tfor ( const result of results ) {\n\t\tif ( result.title ) {\n\t\t\tconst titleTokens = tokenize( result.title );\n\t\t\tconst exactMatchingTokens = titleTokens.filter( ( titleToken ) =>\n\t\t\t\tsearchTokens.some(\n\t\t\t\t\t( searchToken ) => titleToken === searchToken\n\t\t\t\t)\n\t\t\t);\n\t\t\tconst subMatchingTokens = titleTokens.filter( ( titleToken ) =>\n\t\t\t\tsearchTokens.some(\n\t\t\t\t\t( searchToken ) =>\n\t\t\t\t\t\ttitleToken !== searchToken &&\n\t\t\t\t\t\ttitleToken.includes( searchToken )\n\t\t\t\t)\n\t\t\t);\n\n\t\t\t// The score is a combination of exact matches and sub-matches.\n\t\t\t// More weight is given to exact matches, as they are more relevant (e.g. \"cat\" vs \"caterpillar\").\n\t\t\t// Diving by the total number of tokens in the title normalizes the score and skews\n\t\t\t// the results towards shorter titles.\n\t\t\tconst exactMatchScore =\n\t\t\t\t( exactMatchingTokens.length / titleTokens.length ) * 10;\n\n\t\t\tconst subMatchScore = subMatchingTokens.length / titleTokens.length;\n\n\t\t\tscores[ result.id ] = exactMatchScore + subMatchScore;\n\t\t} else {\n\t\t\tscores[ result.id ] = 0;\n\t\t}\n\t}\n\n\treturn results.sort( ( a, b ) => scores[ b.id ] - scores[ a.id ] );\n}\n\n/**\n * Turns text into an array of tokens, with whitespace and punctuation removed.\n *\n * For example, `\"I'm having a ball.\"` becomes `[ \"im\", \"having\", \"a\", \"ball\" ]`.\n *\n * @param text\n */\nexport function tokenize( text: string ): string[] {\n\t// \\p{L} matches any kind of letter from any language.\n\t// \\p{N} matches any kind of numeric character.\n\treturn text.toLowerCase().match( /[\\p{L}\\p{N}]+/gu ) || [];\n}\n"],
"mappings": "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",
"names": ["results"]
}