diff --git a/src/editors/data/constants/problem.ts b/src/editors/data/constants/problem.ts index 0c26a45f0..71989d3d8 100644 --- a/src/editors/data/constants/problem.ts +++ b/src/editors/data/constants/problem.ts @@ -239,3 +239,7 @@ export const ignoredOlxAttributes = [ '@_url_name', '@_x-is-pointer-node', ] as const; + +// Useful for the block creation workflow. +export const problemTitles = new Set([...Object.values(ProblemTypes).map((problem) => problem.title), + ...Object.values(AdvanceProblems).map((problem) => problem.title)]); diff --git a/src/editors/data/redux/thunkActions/requests.js b/src/editors/data/redux/thunkActions/requests.js index c295b3af0..2484de1af 100644 --- a/src/editors/data/redux/thunkActions/requests.js +++ b/src/editors/data/redux/thunkActions/requests.js @@ -15,7 +15,7 @@ import { isLibraryKey } from '../../../../generic/key-utils'; import { createLibraryBlock } from '../../../../library-authoring/data/api'; import { acceptedImgKeys } from '../../../sharedComponents/ImageUploadModal/SelectImageModal/utils'; import { blockTypes } from '../../constants/app'; -import { AdvanceProblems, ProblemTypes } from '../../constants/problem'; +import { problemTitles } from '../../constants/problem'; // Similar to `import { actions, selectors } from '..';` but avoid circular imports: const actions = { requests: requestsActions }; @@ -133,24 +133,24 @@ export const saveBlock = ({ content, ...rest }) => (dispatch, getState) => { * @param {[func]} onFailure - onFailure method ((error) => { ... }) */ export const createBlock = ({ ...rest }) => (dispatch, getState) => { - const blockType = selectors.app.blockType(getState()); const blockTitle = selectors.app.blockTitle(getState()); - let definitionId = blockTitle - ? blockTitle.toLowerCase().replaceAll(' ', '-') - : `${uuid4()}`; - - if (blockType === blockTypes.problem) { - const problemTitles = new Set([...Object.values(ProblemTypes).map((problem) => problem.title), - ...Object.values(AdvanceProblems).map((problem) => problem.title)]); - - definitionId = problemTitles.has(blockTitle) ? `${uuid4()}` : definitionId; + const blockType = selectors.app.blockType(getState()); + // Remove any special character, a slug should be created with unicode letters, numbers, underscores or hyphens. + const cleanTitle = blockTitle?.toLowerCase().replace(/[^a-zA-Z0-9_\s-]/g, '').trim(); + let definitionId; + // Validates if the title has been assigned by the user, if not a UUID is returned as the key. + if (!cleanTitle || (blockType === blockTypes.problem && problemTitles.has(blockTitle))) { + definitionId = `${uuid4()}`; + } else { + // add a hash to prevent duplication. + const hash = uuid4().split('-')[4]; + definitionId = `${cleanTitle.replaceAll(/\s+/g, '-')}-${hash}`; } - dispatch(module.networkRequest({ requestKey: RequestKeys.createBlock, promise: createLibraryBlock({ libraryId: selectors.app.learningContextId(getState()), - blockType: selectors.app.blockType(getState()), + blockType, definitionId, }), ...rest, diff --git a/src/editors/data/redux/thunkActions/requests.test.js b/src/editors/data/redux/thunkActions/requests.test.js index 88b030535..e4728e5c1 100644 --- a/src/editors/data/redux/thunkActions/requests.test.js +++ b/src/editors/data/redux/thunkActions/requests.test.js @@ -18,7 +18,7 @@ jest.mock('../app/selectors', () => ({ blockId: (state) => ({ blockId: state }), blockType: (state) => ({ blockType: state }), learningContextId: (state) => ({ learningContextId: state }), - blockTitle: (state) => state.data, + blockTitle: (state) => state.some, })); jest.mock('../../services/cms/api', () => ({