diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.js b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.js index 0d7b423f6..7f040f6fd 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.js +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/hooks.js @@ -39,23 +39,19 @@ export const setAnswerTitle = ({ }; export const setSelectedFeedback = ({ answer, hasSingleAnswer, dispatch }) => (e) => { - if (e.target) { - dispatch(actions.problem.updateAnswer({ - id: answer.id, - hasSingleAnswer, - selectedFeedback: e.target.value, - })); - } + dispatch(actions.problem.updateAnswer({ + id: answer.id, + hasSingleAnswer, + selectedFeedback: e.target.value, + })); }; export const setUnselectedFeedback = ({ answer, hasSingleAnswer, dispatch }) => (e) => { - if (e.target) { - dispatch(actions.problem.updateAnswer({ - id: answer.id, - hasSingleAnswer, - unselectedFeedback: e.target.value, - })); - } + dispatch(actions.problem.updateAnswer({ + id: answer.id, + hasSingleAnswer, + unselectedFeedback: e.target.value, + })); }; export const useFeedback = (answer) => { diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/__snapshots__/index.test.jsx.snap index 8a9deb930..468b4b388 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/__snapshots__/index.test.jsx.snap @@ -23,7 +23,7 @@ exports[`SolutionWidget render snapshot: renders correct default 1`] = ` /> <[object Object] - editorContentHtml="This is my solution" + editorContentHtml="This is my question" editorType="solution" id="solution" minHeight={150} diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx index 308b1165c..0dccc5a95 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx @@ -6,20 +6,15 @@ import { selectors } from '../../../../../data/redux'; import messages from './messages'; import TinyMceWidget from '../../../../../sharedComponents/TinyMceWidget'; -import { prepareEditorRef, replaceStaticWithAsset } from '../../../../../sharedComponents/TinyMceWidget/hooks'; +import { prepareEditorRef } from '../../../../../sharedComponents/TinyMceWidget/hooks'; export const ExplanationWidget = ({ // redux settings, - learningContextId, // injected intl, }) => { const { editorRef, refReady, setEditorRef } = prepareEditorRef(); - const solutionContent = replaceStaticWithAsset({ - initialContent: settings?.solutionExplanation, - learningContextId, - }); if (!refReady) { return null; } return (
-
-
-
-
useState(val),
});
-export const addImagesAndDimensionsToRef = ({ imagesRef, images, editorContentHtml }) => {
- const imagesWithDimensions = Object.values(images).map((image) => {
+export const addImagesAndDimensionsToRef = ({ imagesRef, assets, editorContentHtml }) => {
+ const imagesWithDimensions = module.filterAssets({ assets }).map((image) => {
const imageFragment = module.getImageFromHtmlString(editorContentHtml, image.url);
return { ...image, width: imageFragment?.width, height: imageFragment?.height };
});
+
imagesRef.current = imagesWithDimensions;
};
-export const useImages = ({ images, editorContentHtml }) => {
+export const useImages = ({ assets, editorContentHtml }) => {
const imagesRef = useRef([]);
useEffect(() => {
- module.addImagesAndDimensionsToRef({ imagesRef, images, editorContentHtml });
- }, [images]);
+ module.addImagesAndDimensionsToRef({ imagesRef, assets, editorContentHtml });
+ }, []);
return { imagesRef };
};
@@ -70,45 +69,45 @@ export const parseContentForLabels = ({ editor, updateContent }) => {
}
};
-export const replaceStaticWithAsset = ({
- initialContent,
- learningContextId,
+export const replaceStaticwithAsset = ({
+ editor,
+ imageUrls,
editorType,
lmsEndpointUrl,
+ updateContent,
}) => {
- let content = initialContent;
- const srcs = content.split(/(src="|src="|href="|href=")/g).filter(
- src => src.startsWith('/static') || src.startsWith('/asset'),
- );
- if (isEmpty(srcs)) {
- return initialContent;
- }
- srcs.forEach(src => {
+ let content = editor.getContent();
+ const imageSrcs = content.split('src="');
+ imageSrcs.forEach(src => {
const currentContent = content;
let staticFullUrl;
const isStatic = src.startsWith('/static/');
- const assetSrc = src.substring(0, src.indexOf('"'));
- const staticName = assetSrc.substring(8);
- const assetName = assetSrc.replace(/\/assets\/.+[^/]\//g, '');
- const displayName = isStatic ? staticName : assetName;
- const isCorrectAssetFormat = assetSrc.match(/\/asset-v1:\S+[+]\S+[@]\S+[+]\S+[@]/g)?.length >= 1;
- // assets in expandable text areas so not support relative urls so all assets must have the lms
- // endpoint prepended to the relative url
- if (editorType === 'expandable') {
- if (isCorrectAssetFormat) {
- staticFullUrl = `${lmsEndpointUrl}${assetSrc}`;
- } else {
- staticFullUrl = `${lmsEndpointUrl}${getRelativeUrl({ courseId: learningContextId, displayName })}`;
+ const isExpandableAsset = src.startsWith('/assets/') && editorType === 'expandable';
+ if ((isStatic || isExpandableAsset) && imageUrls.length > 0) {
+ const assetSrc = src.substring(0, src.indexOf('"'));
+ const assetName = assetSrc.replace(/\/assets\/.+[^/]\//g, '');
+ const staticName = assetSrc.substring(8);
+ imageUrls.forEach((url) => {
+ if (isExpandableAsset && assetName === url.displayName) {
+ staticFullUrl = `${lmsEndpointUrl}${url.staticFullUrl}`;
+ } else if (staticName === url.displayName) {
+ staticFullUrl = url.staticFullUrl;
+ if (isExpandableAsset) {
+ staticFullUrl = `${lmsEndpointUrl}${url.staticFullUrl}`;
+ }
+ }
+ });
+ if (staticFullUrl) {
+ const currentSrc = src.substring(0, src.indexOf('"'));
+ content = currentContent.replace(currentSrc, staticFullUrl);
+ if (editorType === 'expandable') {
+ updateContent(content);
+ } else {
+ editor.setContent(content);
+ }
}
- } else if (!isCorrectAssetFormat) {
- staticFullUrl = getRelativeUrl({ courseId: learningContextId, displayName });
- }
- if (staticFullUrl) {
- const currentSrc = src.substring(0, src.indexOf('"'));
- content = currentContent.replace(currentSrc, staticFullUrl);
}
});
- return content;
};
export const getImageResizeHandler = ({ editor, imagesRef, setImage }) => () => {
@@ -133,10 +132,10 @@ export const setupCustomBehavior = ({
openImgModal,
openSourceCodeModal,
editorType,
+ imageUrls,
images,
setImage,
lmsEndpointUrl,
- learningContextId,
}) => (editor) => {
// image upload button
editor.ui.registry.addButton(tinyMCE.buttons.imageUploadButton, {
@@ -189,24 +188,18 @@ export const setupCustomBehavior = ({
});
if (editorType === 'expandable') {
editor.on('init', () => {
- const initialContent = editor.getContent();
- const newContent = module.replaceStaticWithAsset({
- initialContent,
+ module.replaceStaticwithAsset({
+ editor,
+ imageUrls,
editorType,
lmsEndpointUrl,
- learningContextId,
+ updateContent,
});
- updateContent(newContent);
});
}
editor.on('ExecCommand', (e) => {
if (editorType === 'text' && e.command === 'mceFocus') {
- const initialContent = editor.getContent();
- const newContent = module.replaceStaticWithAsset({
- initialContent,
- learningContextId,
- });
- editor.setContent(newContent);
+ module.replaceStaticwithAsset({ editor, imageUrls });
}
if (e.command === 'RemoveFormat') {
editor.formatter.remove('blockquote');
@@ -236,7 +229,6 @@ export const editorConfig = ({
updateContent,
content,
minHeight,
- learningContextId,
}) => {
const {
toolbar,
@@ -275,7 +267,7 @@ export const editorConfig = ({
setImage: setSelection,
content,
images,
- learningContextId,
+ imageUrls: module.fetchImageUrls(images),
}),
quickbars_insert_toolbar: quickbarsInsertToolbar,
quickbars_selection_toolbar: quickbarsSelectionToolbar,
@@ -388,7 +380,16 @@ export const openModalWithSelectedImage = ({
openImgModal();
};
-export const setAssetToStaticUrl = ({ editorValue, lmsEndpointUrl }) => {
+export const filterAssets = ({ assets }) => {
+ let images = [];
+ const assetsList = Object.values(assets);
+ if (assetsList.length > 0) {
+ images = assetsList.filter(asset => asset?.contentType?.startsWith('image/'));
+ }
+ return images;
+};
+
+export const setAssetToStaticUrl = ({ editorValue, assets, lmsEndpointUrl }) => {
/* For assets to remain usable across course instances, we convert their url to be course-agnostic.
* For example, /assets/course/
test';
- const learningContextId = 'course+test+run';
- const lmsEndpointUrl = 'sOmEvaLue.cOm';
- it('it returns updated src for text editor to update content', () => {
- const expected = '
test';
- const actual = module.replaceStaticWithAsset({ initialContent, learningContextId });
- expect(actual).toEqual(expected);
+ describe('replaceStaticwithAsset', () => {
+ test('it calls getContent and setContent for text editor', () => {
+ const editor = { getContent: jest.fn(() => '
'), setContent: jest.fn() };
+ const imageUrls = [{ staticFullUrl: '/assets/soMEImagEURl1.jpeg', displayName: 'soMEImagEURl1.jpeg' }];
+ const lmsEndpointUrl = 'sOmEvaLue.cOm';
+ module.replaceStaticwithAsset({ editor, imageUrls, lmsEndpointUrl });
+ expect(editor.getContent).toHaveBeenCalled();
+ expect(editor.setContent).toHaveBeenCalled();
});
- it('it returs updated src with absolute url for expandable editor to update content', () => {
+ test('it calls getContent and updateContent for expandable editor', () => {
+ const editor = { getContent: jest.fn(() => '
') };
+ const imageUrls = [{ staticFullUrl: '/assets/soMEImagEURl1.jpeg', displayName: 'soMEImagEURl1.jpeg' }];
+ const lmsEndpointUrl = 'sOmEvaLue.cOm';
const editorType = 'expandable';
- const expected = `
test`;
- const actual = module.replaceStaticWithAsset({
- initialContent,
+ const updateContent = jest.fn();
+ module.replaceStaticwithAsset({
+ editor,
+ imageUrls,
editorType,
lmsEndpointUrl,
- learningContextId,
+ updateContent,
});
- expect(actual).toEqual(expected);
+ expect(editor.getContent).toHaveBeenCalled();
+ expect(updateContent).toHaveBeenCalled();
});
});
describe('setAssetToStaticUrl', () => {
it('returns content with updated img links', () => {
- const editorValue = ' testing link';
+ const editorValue = '
testing link';
+ const assets = [
+ { portableUrl: '/static/soMEImagEURl', displayName: 'soMEImagEURl' },
+ { portableUrl: '/static/soME_ImagE_URl1', displayName: 'soME ImagE URl1' },
+ ];
const lmsEndpointUrl = 'sOmEvaLue.cOm';
- const content = module.setAssetToStaticUrl({ editorValue, lmsEndpointUrl });
+ const content = module.setAssetToStaticUrl({ editorValue, assets, lmsEndpointUrl });
expect(content).toEqual('
testing link');
});
});
@@ -219,7 +228,6 @@ describe('TinyMceEditor hooks', () => {
studioEndpointUrl: 'sOmEoThEruRl.cOm',
images: mockImagesRef,
isLibrary: false,
- learningContextId: 'course+org+run',
};
const evt = 'fakeEvent';
const editor = 'myEditor';
@@ -336,14 +344,27 @@ describe('TinyMceEditor hooks', () => {
openImgModal: props.openImgModal,
openSourceCodeModal: props.openSourceCodeModal,
setImage: props.setSelection,
+ imageUrls: module.fetchImageUrls(props.images),
images: mockImagesRef,
lmsEndpointUrl: props.lmsEndpointUrl,
- learningContextId: props.learningContextId,
}),
);
});
});
+ describe('filterAssets', () => {
+ const emptyAssets = {};
+ const assets = { sOmEaSsET: { contentType: 'image/' } };
+ test('returns an empty array', () => {
+ const emptyFilterAssets = module.filterAssets({ assets: emptyAssets });
+ expect(emptyFilterAssets).toEqual([]);
+ });
+ test('returns filtered array of images', () => {
+ const FilteredAssets = module.filterAssets({ assets });
+ expect(FilteredAssets).toEqual([{ contentType: 'image/' }]);
+ });
+ });
+
describe('imgModalToggle', () => {
const hookKey = state.keys.isImageModalOpen;
beforeEach(() => {
@@ -501,10 +522,11 @@ describe('TinyMceEditor hooks', () => {
describe('addImagesAndDimensionsToRef', () => {
it('should add images to ref', () => {
const imagesRef = { current: null };
+ const assets = { ...mockAssets, height: undefined, width: undefined };
module.addImagesAndDimensionsToRef(
{
imagesRef,
- images: mockImages,
+ assets,
editorContentHtml: mockEditorContentHtml,
},
);
diff --git a/src/editors/sharedComponents/TinyMceWidget/index.jsx b/src/editors/sharedComponents/TinyMceWidget/index.jsx
index 151d08cba..d2c8a3ff1 100644
--- a/src/editors/sharedComponents/TinyMceWidget/index.jsx
+++ b/src/editors/sharedComponents/TinyMceWidget/index.jsx
@@ -41,8 +41,7 @@ export const TinyMceWidget = ({
id,
editorContentHtml, // editorContent in html form
// redux
- learningContextId,
- images,
+ assets,
isLibrary,
lmsEndpointUrl,
studioEndpointUrl,
@@ -51,7 +50,7 @@ export const TinyMceWidget = ({
}) => {
const { isImgOpen, openImgModal, closeImgModal } = hooks.imgModalToggle();
const { isSourceCodeOpen, openSourceCodeModal, closeSourceCodeModal } = hooks.sourceCodeModalToggle(editorRef);
- const { imagesRef } = hooks.useImages({ images, editorContentHtml });
+ const { imagesRef } = hooks.useImages({ assets, editorContentHtml });
const imageSelection = hooks.selectedImage(null);
@@ -86,7 +85,6 @@ export const TinyMceWidget = ({
editorType,
editorRef,
isLibrary,
- learningContextId,
lmsEndpointUrl,
studioEndpointUrl,
images: imagesRef,
@@ -105,7 +103,7 @@ TinyMceWidget.defaultProps = {
editorRef: null,
lmsEndpointUrl: null,
studioEndpointUrl: null,
- images: null,
+ assets: null,
id: null,
disabled: false,
editorContentHtml: undefined,
@@ -114,10 +112,9 @@ TinyMceWidget.defaultProps = {
...editorConfigDefaultProps,
};
TinyMceWidget.propTypes = {
- learningContextId: PropTypes.string,
editorType: PropTypes.string,
isLibrary: PropTypes.bool,
- images: PropTypes.shape({}),
+ assets: PropTypes.shape({}),
editorRef: PropTypes.shape({}),
lmsEndpointUrl: PropTypes.string,
studioEndpointUrl: PropTypes.string,
@@ -130,11 +127,10 @@ TinyMceWidget.propTypes = {
};
export const mapStateToProps = (state) => ({
- images: selectors.app.images(state),
+ assets: selectors.app.assets(state),
lmsEndpointUrl: selectors.app.lmsEndpointUrl(state),
studioEndpointUrl: selectors.app.studioEndpointUrl(state),
isLibrary: selectors.app.isLibrary(state),
- learningContextId: selectors.app.learningContextId(state),
});
export default (connect(mapStateToProps)(TinyMceWidget));
diff --git a/src/editors/sharedComponents/TinyMceWidget/index.test.jsx b/src/editors/sharedComponents/TinyMceWidget/index.test.jsx
index 1b2a51b55..4bfb7a081 100644
--- a/src/editors/sharedComponents/TinyMceWidget/index.test.jsx
+++ b/src/editors/sharedComponents/TinyMceWidget/index.test.jsx
@@ -30,8 +30,7 @@ jest.mock('../../data/redux', () => ({
lmsEndpointUrl: jest.fn(state => ({ lmsEndpointUrl: state })),
studioEndpointUrl: jest.fn(state => ({ studioEndpointUrl: state })),
isLibrary: jest.fn(state => ({ isLibrary: state })),
- images: jest.fn(state => ({ images: state })),
- learningContextId: jest.fn(state => ({ learningContextId: state })),
+ assets: jest.fn(state => ({ assets: state })),
},
},
}));
@@ -53,6 +52,7 @@ jest.mock('./hooks', () => ({
setSelection: jest.fn().mockName('hooks.selectedImage.setSelection'),
clearSelection: jest.fn().mockName('hooks.selectedImage.clearSelection'),
})),
+ filterAssets: jest.fn(() => [{ staTICUrl: staticUrl }]),
useImages: jest.fn(() => ({ imagesRef: { current: [{ externalUrl: staticUrl }] } })),
}));
@@ -70,13 +70,12 @@ describe('TinyMceWidget', () => {
editorType: 'text',
editorRef: { current: { value: 'something' } },
isLibrary: false,
- images: { sOmEaSsET: { staTICUrl: staticUrl } },
+ assets: { sOmEaSsET: { staTICUrl: staticUrl } },
lmsEndpointUrl: 'sOmEvaLue.cOm',
studioEndpointUrl: 'sOmEoThERvaLue.cOm',
disabled: false,
id: 'sOMeiD',
updateContent: () => ({}),
- learningContextId: 'course+org+run',
};
describe('snapshots', () => {
imgModalToggle.mockReturnValue({
@@ -115,20 +114,15 @@ describe('TinyMceWidget', () => {
mapStateToProps(testState).studioEndpointUrl,
).toEqual(selectors.app.studioEndpointUrl(testState));
});
- test('images from app.images', () => {
+ test('assets from app.assets', () => {
expect(
- mapStateToProps(testState).images,
- ).toEqual(selectors.app.images(testState));
+ mapStateToProps(testState).assets,
+ ).toEqual(selectors.app.assets(testState));
});
test('isLibrary from app.isLibrary', () => {
expect(
mapStateToProps(testState).isLibrary,
).toEqual(selectors.app.isLibrary(testState));
});
- test('learningContextId from app.learningContextId', () => {
- expect(
- mapStateToProps(testState).learningContextId,
- ).toEqual(selectors.app.learningContextId(testState));
- });
});
});
diff --git a/src/editors/sharedComponents/TinyMceWidget/utils.js b/src/editors/sharedComponents/TinyMceWidget/utils.js
deleted file mode 100644
index b6e56c655..000000000
--- a/src/editors/sharedComponents/TinyMceWidget/utils.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const getLocatorSafeName = ({ displayName }) => {
- const locatorSafeName = displayName.replace(/[^\w.%-]/gm, '');
- return locatorSafeName;
-};
-
-export const getStaticUrl = ({ displayName }) => (`/static/${getLocatorSafeName({ displayName })}`);
-
-export const getRelativeUrl = ({ courseId, displayName }) => {
- if (displayName) {
- const assetCourseId = courseId.replace('course', 'asset');
- const assetPathShell = `/${assetCourseId}+type@asset+block@`;
- return `${assetPathShell}${displayName}`;
- }
- return '';
-};