UNPKG

@wordpress/editor

Version:
248 lines (241 loc) 9.65 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.FlatTermSelector = FlatTermSelector; exports.default = void 0; var _i18n = require("@wordpress/i18n"); var _element = require("@wordpress/element"); var _components = require("@wordpress/components"); var _data = require("@wordpress/data"); var _deprecated = _interopRequireDefault(require("@wordpress/deprecated")); var _coreData = require("@wordpress/core-data"); var _compose = require("@wordpress/compose"); var _a11y = require("@wordpress/a11y"); var _notices = require("@wordpress/notices"); var _store = require("../../store"); var _terms = require("../../utils/terms"); var _mostUsedTerms = _interopRequireDefault(require("./most-used-terms")); var _jsxRuntime = require("react/jsx-runtime"); /** * WordPress dependencies */ /** * Internal dependencies */ /** * Shared reference to an empty array for cases where it is important to avoid * returning a new array reference on every invocation. * * @type {Array<any>} */const EMPTY_ARRAY = []; /** * How the max suggestions limit was chosen: * - Matches the `per_page` range set by the REST API. * - Can't use "unbound" query. The `FormTokenField` needs a fixed number. * - Matches default for `FormTokenField`. */ const MAX_TERMS_SUGGESTIONS = 100; const DEFAULT_QUERY = { per_page: MAX_TERMS_SUGGESTIONS, _fields: 'id,name', context: 'view' }; const isSameTermName = (termA, termB) => (0, _terms.unescapeString)(termA).toLowerCase() === (0, _terms.unescapeString)(termB).toLowerCase(); const termNamesToIds = (names, terms) => { return names.map(termName => terms.find(term => isSameTermName(term.name, termName))?.id).filter(id => id !== undefined); }; /** * Renders a flat term selector component. * * @param {Object} props The component props. * @param {string} props.slug The slug of the taxonomy. * @param {boolean} props.__nextHasNoMarginBottom Start opting into the new margin-free styles that will become the default in a future version, currently scheduled to be WordPress 7.0. (The prop can be safely removed once this happens.) * * @return {JSX.Element} The rendered flat term selector component. */ function FlatTermSelector({ slug, __nextHasNoMarginBottom }) { var _taxonomy$labels$add_, _taxonomy$labels$sing2; const [values, setValues] = (0, _element.useState)([]); const [search, setSearch] = (0, _element.useState)(''); const debouncedSearch = (0, _compose.useDebounce)(setSearch, 500); if (!__nextHasNoMarginBottom) { (0, _deprecated.default)('Bottom margin styles for wp.editor.PostTaxonomiesFlatTermSelector', { since: '6.7', version: '7.0', hint: 'Set the `__nextHasNoMarginBottom` prop to true to start opting into the new styles, which will become the default in a future version.' }); } const { terms, termIds, taxonomy, hasAssignAction, hasCreateAction, hasResolvedTerms } = (0, _data.useSelect)(select => { var _post$_links, _post$_links2; const { getCurrentPost, getEditedPostAttribute } = select(_store.store); const { getEntityRecords, getTaxonomy, hasFinishedResolution } = select(_coreData.store); const post = getCurrentPost(); const _taxonomy = getTaxonomy(slug); const _termIds = _taxonomy ? getEditedPostAttribute(_taxonomy.rest_base) : EMPTY_ARRAY; const query = { ...DEFAULT_QUERY, include: _termIds?.join(','), per_page: -1 }; return { hasCreateAction: _taxonomy ? (_post$_links = post._links?.['wp:action-create-' + _taxonomy.rest_base]) !== null && _post$_links !== void 0 ? _post$_links : false : false, hasAssignAction: _taxonomy ? (_post$_links2 = post._links?.['wp:action-assign-' + _taxonomy.rest_base]) !== null && _post$_links2 !== void 0 ? _post$_links2 : false : false, taxonomy: _taxonomy, termIds: _termIds, terms: _termIds?.length ? getEntityRecords('taxonomy', slug, query) : EMPTY_ARRAY, hasResolvedTerms: hasFinishedResolution('getEntityRecords', ['taxonomy', slug, query]) }; }, [slug]); const { searchResults } = (0, _data.useSelect)(select => { const { getEntityRecords } = select(_coreData.store); return { searchResults: !!search ? getEntityRecords('taxonomy', slug, { ...DEFAULT_QUERY, search }) : EMPTY_ARRAY }; }, [search, slug]); // Update terms state only after the selectors are resolved. // We're using this to avoid terms temporarily disappearing on slow networks // while core data makes REST API requests. (0, _element.useEffect)(() => { if (hasResolvedTerms) { const newValues = (terms !== null && terms !== void 0 ? terms : []).map(term => (0, _terms.unescapeString)(term.name)); setValues(newValues); } }, [terms, hasResolvedTerms]); const suggestions = (0, _element.useMemo)(() => { return (searchResults !== null && searchResults !== void 0 ? searchResults : []).map(term => (0, _terms.unescapeString)(term.name)); }, [searchResults]); const { editPost } = (0, _data.useDispatch)(_store.store); const { saveEntityRecord } = (0, _data.useDispatch)(_coreData.store); const { createErrorNotice } = (0, _data.useDispatch)(_notices.store); if (!hasAssignAction) { return null; } async function findOrCreateTerm(term) { try { const newTerm = await saveEntityRecord('taxonomy', slug, term, { throwOnError: true }); return (0, _terms.unescapeTerm)(newTerm); } catch (error) { if (error.code !== 'term_exists') { throw error; } return { id: error.data.term_id, name: term.name }; } } function onUpdateTerms(newTermIds) { editPost({ [taxonomy.rest_base]: newTermIds }); } function onChange(termNames) { const availableTerms = [...(terms !== null && terms !== void 0 ? terms : []), ...(searchResults !== null && searchResults !== void 0 ? searchResults : [])]; const uniqueTerms = termNames.reduce((acc, name) => { if (!acc.some(n => n.toLowerCase() === name.toLowerCase())) { acc.push(name); } return acc; }, []); const newTermNames = uniqueTerms.filter(termName => !availableTerms.find(term => isSameTermName(term.name, termName))); // Optimistically update term values. // The selector will always re-fetch terms later. setValues(uniqueTerms); if (newTermNames.length === 0) { onUpdateTerms(termNamesToIds(uniqueTerms, availableTerms)); return; } if (!hasCreateAction) { return; } Promise.all(newTermNames.map(termName => findOrCreateTerm({ name: termName }))).then(newTerms => { const newAvailableTerms = availableTerms.concat(newTerms); onUpdateTerms(termNamesToIds(uniqueTerms, newAvailableTerms)); }).catch(error => { createErrorNotice(error.message, { type: 'snackbar' }); // In case of a failure, try assigning available terms. // This will invalidate the optimistic update. onUpdateTerms(termNamesToIds(uniqueTerms, availableTerms)); }); } function appendTerm(newTerm) { var _taxonomy$labels$sing; if (termIds.includes(newTerm.id)) { return; } const newTermIds = [...termIds, newTerm.id]; const defaultName = slug === 'post_tag' ? (0, _i18n.__)('Tag') : (0, _i18n.__)('Term'); const termAddedMessage = (0, _i18n.sprintf)( /* translators: %s: term name. */ (0, _i18n._x)('%s added', 'term'), (_taxonomy$labels$sing = taxonomy?.labels?.singular_name) !== null && _taxonomy$labels$sing !== void 0 ? _taxonomy$labels$sing : defaultName); (0, _a11y.speak)(termAddedMessage, 'assertive'); onUpdateTerms(newTermIds); } const newTermLabel = (_taxonomy$labels$add_ = taxonomy?.labels?.add_new_item) !== null && _taxonomy$labels$add_ !== void 0 ? _taxonomy$labels$add_ : slug === 'post_tag' ? (0, _i18n.__)('Add new tag') : (0, _i18n.__)('Add new Term'); const singularName = (_taxonomy$labels$sing2 = taxonomy?.labels?.singular_name) !== null && _taxonomy$labels$sing2 !== void 0 ? _taxonomy$labels$sing2 : slug === 'post_tag' ? (0, _i18n.__)('Tag') : (0, _i18n.__)('Term'); const termAddedLabel = (0, _i18n.sprintf)( /* translators: %s: term name. */ (0, _i18n._x)('%s added', 'term'), singularName); const termRemovedLabel = (0, _i18n.sprintf)( /* translators: %s: term name. */ (0, _i18n._x)('%s removed', 'term'), singularName); const removeTermLabel = (0, _i18n.sprintf)( /* translators: %s: term name. */ (0, _i18n._x)('Remove %s', 'term'), singularName); return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_components.FormTokenField, { __next40pxDefaultSize: true, value: values, suggestions: suggestions, onChange: onChange, onInputChange: debouncedSearch, maxSuggestions: MAX_TERMS_SUGGESTIONS, label: newTermLabel, messages: { added: termAddedLabel, removed: termRemovedLabel, remove: removeTermLabel }, __nextHasNoMarginBottom: __nextHasNoMarginBottom }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_mostUsedTerms.default, { taxonomy: taxonomy, onSelect: appendTerm })] }); } var _default = exports.default = (0, _components.withFilters)('editor.PostTaxonomyType')(FlatTermSelector); //# sourceMappingURL=flat-term-selector.js.map