diff --git a/admin/src/components/PermalinkInput/index.js b/admin/src/components/PermalinkInput/index.js index dc4a6a3..8531845 100644 --- a/admin/src/components/PermalinkInput/index.js +++ b/admin/src/components/PermalinkInput/index.js @@ -67,6 +67,7 @@ const PermalinkInput = forwardRef((props, ref) => { !isCreatingEntry && !hasDifferentRelationUID && targetRelationValue?.id === modifiedData.id; const initialValue = initialData[name]; + const locale = initialData.locale; const initialRelationValue = getRelationValue(initialData, targetFieldConfig.targetRelation); const initialAncestorsPath = getPermalinkAncestors(initialValue); const initialSlug = getPermalinkSlug(initialValue); @@ -140,10 +141,14 @@ const PermalinkInput = forwardRef((props, ref) => { if (!newSlug) { setIsLoading(false); + return; } - const params = `${contentTypeUID}/${encodeURIComponent(newSlug)}`; + const params = `${contentTypeUID}/${encodeURIComponent(newSlug)}${ + locale ? `/${locale}` : '' + }`; + const endpoint = getApiUrl(`${pluginId}/check-availability/${params}`); const { data } = await fetchClient.get(endpoint); @@ -220,6 +225,7 @@ const PermalinkInput = forwardRef((props, ref) => { if (!targetRelationValue) { removeAncestorsPath(); setIsLoading(false); + return; } @@ -322,6 +328,7 @@ const PermalinkInput = forwardRef((props, ref) => { setIsOrphan(false); setAncestorsPath(null); setFieldError(null); + return; } @@ -451,24 +458,6 @@ const PermalinkInput = forwardRef((props, ref) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [isCreatingEntry, isCustomized, debouncedTargetValue]); - /* - This use effect clashes with the check connection use effect, - effectively overwriting the ancestors path when changing locales. - I am leaving this here for now as I dont know what other potential - side effects this has, but it is not needed for the ancestors path - to be correctly set. - */ - - // useEffect(() => { - // // This is required for scenarios like switching between locales to ensure - // // the field value updates with the locale change. - // const newAncestorsPath = getPermalinkAncestors(initialValue); - // const newSlug = getPermalinkSlug(initialValue); - - // setFieldState(newAncestorsPath, newSlug, true); - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, [initialData.id]); - useEffect(() => { // Remove ancestors path if we have selected the current entity as the parent. if (selectedSelfRelation) { diff --git a/admin/src/contentManagerHooks/filter-permalink-columns.js b/admin/src/contentManagerHooks/filter-permalink-columns.js index 16bdbd2..cf1b2ab 100644 --- a/admin/src/contentManagerHooks/filter-permalink-columns.js +++ b/admin/src/contentManagerHooks/filter-permalink-columns.js @@ -16,7 +16,7 @@ const filterPermalinkColumns = ({ displayedHeaders, layout }) => { return { ...header, - cellFormatter: (props) => { + cellFormatter(props) { const value = props[header.name]; const ancestorsPath = getPermalinkAncestors(value); const slug = getPermalinkSlug(value); diff --git a/admin/src/hooks/use-permalink.js b/admin/src/hooks/use-permalink.js index f95a08a..18ade5b 100644 --- a/admin/src/hooks/use-permalink.js +++ b/admin/src/hooks/use-permalink.js @@ -37,7 +37,7 @@ const usePermalink = (uid, data, isCreatingEntry) => { } setUrl(url); - setCopy(state?.copy === false ? false : true); + setCopy(state?.copy !== false); }, [contentTypes, data, uid, setCopy, setUrl, runHookWaterfall]); useEffect(() => { diff --git a/server/config.js b/server/config.js index 47ff1c7..c6f6ac1 100644 --- a/server/config.js +++ b/server/config.js @@ -7,7 +7,7 @@ module.exports = { contentTypes: [], lowercase: true, }, - validator: (config) => { + validator(config) { if (!config.contentTypes) { return; } diff --git a/server/controllers/permalinks.js b/server/controllers/permalinks.js index e35c37d..f4b4b3a 100644 --- a/server/controllers/permalinks.js +++ b/server/controllers/permalinks.js @@ -63,16 +63,16 @@ module.exports = { }, async checkAvailability(ctx) { - const { uid, value } = ctx.request.params; + const { uid, value, locale } = ctx.request.params; const decodedValue = decodeURIComponent(value); - // Validate UID. await getService('validation').validateUIDInput(uid); // Check availability and maybe provide a suggestion. const { isAvailable, suggestion } = await getService('permalinks').getAvailability( uid, - decodedValue + decodedValue, + locale ); ctx.send({ diff --git a/server/lifecycles/before-create-update.js b/server/lifecycles/before-create-update.js index c95ed4b..5c96cfa 100644 --- a/server/lifecycles/before-create-update.js +++ b/server/lifecycles/before-create-update.js @@ -17,6 +17,12 @@ module.exports = async ({ strapi }) => { const id = get(where, 'id', null); const attr = layouts[uid]; const value = data[attr.name]; + let locale = data.locale || undefined; + + if (id && !locale && model.attributes.locale) { + const entity = await strapi.db.query(uid).findOne({ where: { id } }); + locale = entity.locale; + } await getService('validation').validateFormat(uid, value); await getService('validation').validateConnection(uid, data, id); @@ -27,7 +33,7 @@ module.exports = async ({ strapi }) => { const { name } = layouts[uid]; return getService('validation') - .validateAvailability(uid, name, value, id) + .validateAvailability(uid, name, value, id, locale) .then((available) => ({ uid, available, diff --git a/server/routes/admin-api.js b/server/routes/admin-api.js index f61d13e..6d6df9e 100644 --- a/server/routes/admin-api.js +++ b/server/routes/admin-api.js @@ -29,7 +29,7 @@ module.exports = { }, { method: 'GET', - path: '/check-availability/:uid/:value', + path: '/check-availability/:uid/:value/:locale?', handler: 'permalinks.checkAvailability', config: { policies: ['admin::isAuthenticatedAdmin'], diff --git a/server/services/permalinks.js b/server/services/permalinks.js index 8ddc095..0baa137 100644 --- a/server/services/permalinks.js +++ b/server/services/permalinks.js @@ -29,7 +29,7 @@ module.exports = ({ strapi }) => ({ return path; }, - async getAvailability(uid, value) { + async getAvailability(uid, value, locale) { const layouts = await getService('config').layouts(); const uids = await getService('config').uids(uid); @@ -37,9 +37,8 @@ module.exports = ({ strapi }) => ({ const promisedAvailables = await Promise.all( uids.map((_uid) => { const { name } = layouts[_uid]; - return getService('validation') - .validateAvailability(_uid, name, value) + .validateAvailability(_uid, name, value, null, locale) .then((available) => ({ uid: _uid, available, @@ -74,7 +73,8 @@ module.exports = ({ strapi }) => ({ return null; } - let entity, ancestor; + let entity; + let ancestor; // Either get the ancestor from the connecting relation or look it up in the database. if (isConnecting) { diff --git a/server/services/validation.js b/server/services/validation.js index db124b0..53b4129 100644 --- a/server/services/validation.js +++ b/server/services/validation.js @@ -33,9 +33,16 @@ module.exports = ({ strapi }) => ({ return false; }, - async validateAvailability(uid, name, value, id = null) { - let where = { [name]: value }; + async validateAvailability(uid, name, value, id = null, locale = undefined) { + const model = strapi.db.metadata.get(uid); + const { pluginOptions } = model; + + const { i18n } = pluginOptions; + + const isLocalized = (i18n && i18n.localized) || false; + + const where = { [name]: value }; // If `id` is not null, omit it from the results so we aren't comparing against itself. if (id) { where.id = { @@ -43,9 +50,16 @@ module.exports = ({ strapi }) => ({ }; } + // takes the locale into consideration for uniqueness, if the locale is present, and the model is localized + if (locale && isLocalized) { + where.locale = { + $eq: locale, + }; + } + const count = await strapi.db.query(uid).count({ where }); - return count > 0 ? false : true; + return !(count > 0); }, async validateConnection(uid, data, id = null) { @@ -124,9 +138,9 @@ module.exports = ({ strapi }) => ({ const { attributes } = model; // Ensure that exactly one permalink attribute is defined for this model. - const permalinkAttrs = Object.entries(attributes).filter(([, attr]) => { - return attr.customField === UID_PERMALINK_FIELD; - }); + const permalinkAttrs = Object.entries(attributes).filter( + ([, attr]) => attr.customField === UID_PERMALINK_FIELD + ); if (permalinkAttrs.length !== 1) { throw new ValidationError(`Must have exactly one permalink attribute defined in ${uid}.`); diff --git a/server/utils/get-permalink-attr.js b/server/utils/get-permalink-attr.js index 6d8f470..ce3f311 100644 --- a/server/utils/get-permalink-attr.js +++ b/server/utils/get-permalink-attr.js @@ -8,9 +8,9 @@ const pluginId = require('./plugin-id'); const getPermalinkAttr = (uid) => { const model = strapi.getModel(uid); - const permalinkAttr = Object.entries(model.attributes).find(([, attr]) => { - return attr.customField === UID_PERMALINK_FIELD; - }); + const permalinkAttr = Object.entries(model.attributes).find( + ([, attr]) => attr.customField === UID_PERMALINK_FIELD + ); if (!permalinkAttr) { return null; diff --git a/server/utils/get-permalink-slug.js b/server/utils/get-permalink-slug.js index a21a996..af3c6b8 100644 --- a/server/utils/get-permalink-slug.js +++ b/server/utils/get-permalink-slug.js @@ -1,12 +1,11 @@ 'use strict'; -const getPermalinkSlug = (path) => { - return path +const getPermalinkSlug = (path) => + path ? path .split('/') .filter((i) => i) .reverse()[0] : ''; -}; module.exports = getPermalinkSlug;