diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx index 076e0de05..94535e18d 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx @@ -10,11 +10,11 @@ import './index.scss'; import TinyMceWidget from '../../../../../sharedComponents/TinyMceWidget'; export const QuestionWidget = ({ + assets, // redux isLibrary, question, updateQuestion, - assets, lmsEndpointUrl, studioEndpointUrl, // injected @@ -46,21 +46,20 @@ export const QuestionWidget = ({ QuestionWidget.defaultProps = { isLibrary: null, - assets: null, }; + QuestionWidget.propTypes = { + assets: PropTypes.shape({}).isRequired, // redux isLibrary: PropTypes.bool, lmsEndpointUrl: PropTypes.string.isRequired, studioEndpointUrl: PropTypes.string.isRequired, - assets: PropTypes.shape({}), question: PropTypes.string.isRequired, updateQuestion: PropTypes.func.isRequired, // injected intl: intlShape.isRequired, }; export const mapStateToProps = (state) => ({ - assets: selectors.app.assets(state), isLibrary: selectors.app.isLibrary(state), question: selectors.problem.question(state), lmsEndpointUrl: selectors.app.lmsEndpointUrl(state), diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx index 10e60fa67..6856a442c 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx @@ -16,7 +16,6 @@ jest.mock('../../../../../data/redux', () => ({ isLibrary: jest.fn(state => ({ isLibrary: state })), lmsEndpointUrl: jest.fn(state => ({ lmsEndpointUrl: state })), studioEndpointUrl: jest.fn(state => ({ studioEndpointUrl: state })), - assets: jest.fn(state => ({ assets: state })), }, problem: { question: jest.fn(state => ({ question: state })), @@ -53,11 +52,6 @@ describe('QuestionWidget', () => { test('isLibrary from app.isLibrary', () => { expect(mapStateToProps(testState).isLibrary).toEqual(selectors.app.isLibrary(testState)); }); - test('assets from app.assets', () => { - expect( - mapStateToProps(testState).assets, - ).toEqual(selectors.app.assets(testState)); - }); test('question from problem.question', () => { expect(mapStateToProps(testState).question).toEqual(selectors.problem.question(testState)); }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/__snapshots__/index.test.jsx.snap index 245bb0ad1..61f4687f4 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/__snapshots__/index.test.jsx.snap @@ -42,7 +42,9 @@ exports[`EditorProblemView component renders simple view 1`] = ` - + diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/hooks.js b/src/editors/containers/ProblemEditor/components/EditProblemView/hooks.js index 80298bc56..6eda62f91 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/hooks.js +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/hooks.js @@ -1,14 +1,21 @@ import ReactStateSettingsParser from '../../data/ReactStateSettingsParser'; import ReactStateOLXParser from '../../data/ReactStateOLXParser'; +import { setAssetToStaticUrl } from '../../../../sharedComponents/TinyMceWidget/hooks'; // eslint-disable-next-line import/prefer-default-export -export const parseState = (problem, isAdvanced, ref) => () => { +export const parseState = ({ + problem, + isAdvanced, + ref, + assets, +}) => () => { const reactSettingsParser = new ReactStateSettingsParser(problem); const reactOLXParser = new ReactStateOLXParser({ problem }); + const reactBuiltOlx = setAssetToStaticUrl({ editorValue: reactOLXParser.buildOLX(), assets }); const rawOLX = ref?.current?.state.doc.toString(); return { settings: reactSettingsParser.getSettings(), - olx: isAdvanced ? rawOLX : reactOLXParser.buildOLX(), + olx: isAdvanced ? rawOLX : reactBuiltOlx, }; }; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/hooks.test.js b/src/editors/containers/ProblemEditor/components/EditProblemView/hooks.test.js index 9cfc8a42f..6591030a3 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/hooks.test.js +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/hooks.test.js @@ -15,11 +15,21 @@ describe('EditProblemView hooks parseState', () => { const refMock = { current: { state: { doc: { toString: toStringMock } } } }; test('default problem', () => { - const res = hooks.parseState('problem', false, refMock)(); + const res = hooks.parseState({ + problem: 'problem', + isAdvanced: false, + ref: refMock, + assets: {}, + })(); expect(res.olx).toBe(mockBuiltOLX); }); test('advanced problem', () => { - const res = hooks.parseState('problem', true, refMock)(); + const res = hooks.parseState({ + problem: 'problem', + isAdvanced: true, + ref: refMock, + assets: {}, + })(); expect(res.olx).toBe(mockRawOLX); }); }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/index.jsx index c745e122b..c11a329a6 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/index.jsx @@ -19,11 +19,17 @@ export const EditProblemView = ({ // redux problemType, problemState, + assets, }) => { const editorRef = useRef(null); const isAdvancedProblemType = problemType === ProblemTypeKeys.ADVANCED; - const getContent = parseState(problemState, isAdvancedProblemType, editorRef); + const getContent = parseState({ + problem: problemState, + isAdvanced: isAdvancedProblemType, + ref: editorRef, + assets, + }); return ( @@ -34,7 +40,7 @@ export const EditProblemView = ({ ) : ( - + )} @@ -46,13 +52,19 @@ export const EditProblemView = ({ ); }; +EditProblemView.defaultProps = { + assets: null, +}; + EditProblemView.propTypes = { problemType: PropTypes.string.isRequired, // eslint-disable-next-line problemState: PropTypes.any.isRequired, + assets: PropTypes.shape({}), }; export const mapStateToProps = (state) => ({ + assets: selectors.app.assets(state), problemType: selectors.problem.problemType(state), problemState: selectors.problem.completeState(state), }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/index.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/index.test.jsx index 54f1c5bfb..394296a96 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/index.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/index.test.jsx @@ -1,3 +1,4 @@ +import React from 'react'; import { shallow } from 'enzyme'; import { EditProblemView } from '.'; import AnswerWidget from './AnswerWidget'; @@ -9,13 +10,15 @@ describe('EditorProblemView component', () => { const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); expect(wrapper.find(AnswerWidget).length).toBe(1); + expect(wrapper.find(RawEditor).length).toBe(0); }); test('renders raw editor', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); expect(wrapper.find(AnswerWidget).length).toBe(0); expect(wrapper.find(RawEditor).length).toBe(1); diff --git a/src/editors/containers/TextEditor/hooks.js b/src/editors/containers/TextEditor/hooks.js index 5d0bb6aeb..dc986d033 100644 --- a/src/editors/containers/TextEditor/hooks.js +++ b/src/editors/containers/TextEditor/hooks.js @@ -5,6 +5,7 @@ import { import { StrictDict } from '../../utils'; import * as appHooks from '../../hooks'; import * as module from './hooks'; +import { setAssetToStaticUrl } from '../../sharedComponents/TinyMceWidget/hooks'; export const { nullMethod, navigateCallback, navigateTo } = appHooks; @@ -22,41 +23,6 @@ export const prepareEditorRef = () => { return { editorRef, refReady, setEditorRef }; }; -export const setAssetToStaticUrl = ({ editorValue, assets }) => { - /* For assets to remain usable across course instances, we convert their url to be course-agnostic. - * For example, /assets/course//filename gets converted to /static/filename. This is - * important for rerunning courses and importing/exporting course as the /static/ part of the url - * allows the asset to be mapped to the new course run. - */ - let content = editorValue; - const assetUrls = []; - const assetsList = Object.values(assets); - assetsList.forEach(asset => { - assetUrls.push({ portableUrl: asset.portableUrl, displayName: asset.displayName }); - }); - const assetSrcs = typeof content === 'string' ? content.split(/(src="|href=")/g) : []; - assetSrcs.forEach(src => { - if (src.startsWith('/asset') && assetUrls.length > 0) { - const assetBlockName = src.substring(src.indexOf('@') + 1, src.indexOf('"')); - const nameFromEditorSrc = assetBlockName.substring(assetBlockName.indexOf('@') + 1); - const nameFromStudioSrc = assetBlockName.substring(assetBlockName.indexOf('/') + 1); - let portableUrl; - assetUrls.forEach((url) => { - const displayName = url.displayName.replace(/\s/g, '_'); - if (displayName === nameFromEditorSrc || displayName === nameFromStudioSrc) { - portableUrl = url.portableUrl; - } - }); - if (portableUrl) { - const currentSrc = src.substring(0, src.indexOf('"')); - const updatedContent = content.replace(currentSrc, portableUrl); - content = updatedContent; - } - } - }); - return content; -}; - export const getContent = ({ editorRef, isRaw, assets }) => () => { const content = (isRaw && editorRef && editorRef.current ? editorRef.current.state.doc.toString() diff --git a/src/editors/containers/TextEditor/hooks.test.jsx b/src/editors/containers/TextEditor/hooks.test.jsx index 885256e10..aadbad8c8 100644 --- a/src/editors/containers/TextEditor/hooks.test.jsx +++ b/src/editors/containers/TextEditor/hooks.test.jsx @@ -63,18 +63,6 @@ describe('TextEditor hooks', () => { }); }); - describe('setAssetToStaticUrl', () => { - it('returns content with updated img links', () => { - const editorValue = ' testing link'; - const assets = [ - { portableUrl: '/static/soMEImagEURl', displayName: 'soMEImagEURl' }, - { portableUrl: '/static/soME_ImagE_URl1', displayName: 'soME ImagE URl1' }, - ]; - const content = module.setAssetToStaticUrl({ editorValue, assets }); - expect(content).toEqual(' testing link'); - }); - }); - describe('getContent', () => { const visualContent = 'sOmEViSualContent'; const rawContent = 'soMeRawContent'; diff --git a/src/editors/sharedComponents/TinyMceWidget/hooks.js b/src/editors/sharedComponents/TinyMceWidget/hooks.js index 30c809ab0..ce6500fa2 100644 --- a/src/editors/sharedComponents/TinyMceWidget/hooks.js +++ b/src/editors/sharedComponents/TinyMceWidget/hooks.js @@ -73,6 +73,7 @@ export const setupCustomBehavior = ({ openSourceCodeModal, setImage, editorType, + imageUrls, }) => (editor) => { // image upload button editor.ui.registry.addButton(tinyMCE.buttons.imageUploadButton, { @@ -129,13 +130,14 @@ export const setupCustomBehavior = ({ }); } }); -}; - -export const checkRelativeUrl = (imageUrls) => (editor) => { editor.on('ExecCommand', (e) => { if (e.command === 'mceFocus') { module.replaceStaticwithAsset(editor, imageUrls); } + if (e.command === 'RemoveFormat') { + editor.formatter.remove('blockquote'); + editor.formatter.remove('label'); + } }); }; @@ -180,7 +182,6 @@ export const editorConfig = ({ min_height: minHeight, contextmenu: 'link table', document_base_url: lmsEndpointUrl, - init_instance_callback: module.checkRelativeUrl(module.fetchImageUrls(images)), imagetools_cors_hosts: [removeProtocolFromUrl(lmsEndpointUrl), removeProtocolFromUrl(studioEndpointUrl)], imagetools_toolbar: imageToolbar, formats: { label: { inline: 'label' } }, @@ -190,6 +191,7 @@ export const editorConfig = ({ openImgModal, openSourceCodeModal, setImage: setSelection, + imageUrls: module.fetchImageUrls(images), }), toolbar, plugins, diff --git a/src/editors/sharedComponents/TinyMceWidget/hooks.test.js b/src/editors/sharedComponents/TinyMceWidget/hooks.test.js index 478f1dff7..e16cdb5c6 100644 --- a/src/editors/sharedComponents/TinyMceWidget/hooks.test.js +++ b/src/editors/sharedComponents/TinyMceWidget/hooks.test.js @@ -120,14 +120,6 @@ describe('TinyMceEditor hooks', () => { expect(content).toEqual(' testing link'); }); }); - describe('checkRelativeUrl', () => { - test('it calls editor.on', () => { - const editor = { on: jest.fn() }; - const imageUrls = ['soMEImagEURl1']; - module.checkRelativeUrl(imageUrls)(editor); - expect(editor.on).toHaveBeenCalled(); - }); - }); describe('editorConfig', () => { const props = { @@ -211,10 +203,6 @@ describe('TinyMceEditor hooks', () => { expect(output.initialValue).toBe(textValue); }); - // test('It configures plugins and toolbars correctly', () => { - // expect(output.init.plugins).toEqual('autoresize'); - // expect(output.init.toolbar).toEqual(`${pluginConfig().toolbar} | customLabelButton`); - // }); it('calls setupCustomBehavior on setup', () => { expect(output.init.setup).toEqual( setupCustomBehavior({ @@ -223,6 +211,7 @@ describe('TinyMceEditor hooks', () => { openImgModal: props.openImgModal, openSourceCodeModal: props.openSourceCodeModal, setImage: props.setSelection, + imageUrls: module.fetchImageUrls(props.images), }), ); });