@wordpress/editor
Version:
Enhanced block editor for WordPress posts.
248 lines (241 loc) • 9.65 kB
JavaScript
"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