/** * External dependencies. */ import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; /** * WordPress dependencies. */ import apiFetch from '@wordpress/api-fetch'; import { Component, Fragment } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies. */ import { pluck, debounce } from '../utils/helpers'; apiFetch.use(apiFetch.createRootURLMiddleware(window.epOrdering.restApiRoot)); export class Pointers extends Component { titleInput = null; debouncedDefaultResults = debounce(() => { this.getDefaultResults(); }, 200); doSearch = debounce(() => { const { searchText, searchResults } = this.state; const searchTerm = searchText; // Set loading state searchResults[searchTerm] = false; this.setState({ searchResults }); apiFetch({ path: `/elasticpress/v1/pointer_search?s=${searchTerm}`, }).then((result) => { searchResults[searchTerm] = result; this.setState({ searchResults }); }); }, 200); debouncedHandleTitleChange = debounce(() => { this.handleTitleChange(); }, 200); /** * Initializes the component with initial state set by WP * * @param {object} props Component props */ constructor(props) { super(props); // We need to know the title of the page and react to changes since this is the query we search for this.titleInput = document.getElementById('title'); this.state = { pointers: window.epOrdering.pointers, posts: window.epOrdering.posts, title: this.titleInput.value, defaultResults: {}, searchText: '', searchResults: {}, }; } componentDidMount() { this.titleInput.addEventListener('keyup', this.debouncedHandleTitleChange); const { title } = this.state; if (title?.length > 0) { this.getDefaultResults(); } } componentWillUnmount() { this.titleInput.removeEventListener('keyup', this.debouncedHandleTitleChange); } handleTitleChange = () => { this.setState({ title: this.titleInput.value }); this.debouncedDefaultResults(); }; getDefaultResults = () => { const { title: searchTerm } = this.state; apiFetch({ path: `/elasticpress/v1/pointer_preview?s=${searchTerm}`, }).then((result) => { const { defaultResults } = this.state; defaultResults[searchTerm] = result; this.setState({ defaultResults }); }); }; removePointer = (pointer) => { let { pointers } = this.state; delete pointers[pointers.indexOf(pointer)]; pointers = pointers.filter((item) => item !== null); this.setState({ pointers }); }; getMergedPosts = () => { let { pointers } = this.state; const { title, defaultResults } = this.state; let merged = defaultResults[title].slice(); const setIds = {}; merged.forEach((item) => { setIds[item.ID] = item; }); pointers = pointers.sort((a, b) => { return a.order > b.order ? 1 : -1; }); pointers.forEach((pointer) => { // Remove the original if a duplicate if (setIds[pointer.ID]) { delete merged[merged.indexOf(setIds[pointer.ID])]; merged = merged.filter((item) => item); } // Insert into proper location merged.splice(parseInt(pointer.order, 10) - 1, 0, pointer); }); return merged; }; /** * Gets the next available position for a pointer * * @returns {number|false} The available position */ getNextAvailablePosition = () => { const { pointers } = this.state; const availablePositions = {}; for (let i = 1; i <= window.epOrdering.postsPerPage; i++) { availablePositions[i] = true; } pointers.forEach((item) => { delete availablePositions[item.order]; }); const keys = Object.keys(availablePositions); if (keys.length === 0) { return false; } return parseInt(keys[0], 10); }; /** * Adds a new pointer. We place the new pointer at the highest available position * * @param {object} post Post object */ addPointer = (post) => { const id = post.ID; const { posts, pointers } = this.state; if (!posts[id]) { posts[id] = post; this.setState({ posts }); } const position = this.getNextAvailablePosition(); if (!position) { /* eslint-disable no-alert */ window.alert( __('You have added the maximum number of custom results.', 'elasticpress'), ); /* eslint-enable no-alert */ return; } pointers.push({ ID: id, order: position, }); this.setState({ pointers }); }; /** * Callback when drag/drop is complete. * * Only the pointers are able to be dragged around, so all we need to do is increase any pointer by one that is * either at the current position or greater * * @param {object} result Dragged object */ onDragComplete = (result) => { // dropped outside the list if (!result.destination) { return; } const items = this.getMergedPosts(); // Offsetting indexes when over posts per page to account for the non-sortable notice const ppp = parseInt(window.epOrdering.postsPerPage, 10); const startIndex = result.source.index >= ppp ? result.source.index - 1 : result.source.index; const endIndex = result.destination.index > ppp ? result.destination.index - 1 : result.destination.index; const [removed] = items.splice(startIndex, 1); items.splice(endIndex, 0, removed); // Now _all_ the items are in order - grab the pointers and set the new positions to state const pointers = []; items.forEach((item, index) => { if (item.order) { // Reordering an existing pointer pointers.push({ ID: item.ID, order: index + 1, }); } else if (item.ID === result.draggableId) { // Adding a default post to the pointers array pointers.push({ ID: item.ID, order: index + 1, }); } }); this.setState({ pointers }); }; searchResults = (searchResults) => { const { searchText } = this.state; if (searchText === '') { return null; } if (searchResults === false) { return (
{__( 'Enter your search query above to preview the results.', 'elasticpress', )}