diff --git a/src/editors/containers/TextEditor/index.jsx b/src/editors/containers/TextEditor/index.jsx index c4b2215e93..b3368152b3 100644 --- a/src/editors/containers/TextEditor/index.jsx +++ b/src/editors/containers/TextEditor/index.jsx @@ -8,6 +8,7 @@ import { } from '@openedx/paragon'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { getConfig } from '@edx/frontend-platform'; import { actions, selectors } from '../../data/redux'; import { RequestKeys } from '../../data/constants/requests'; @@ -24,6 +25,7 @@ const TextEditor = ({ // redux showRawEditor, blockValue, + blockId, blockFailed, initializeEditor, blockFinished, @@ -40,6 +42,10 @@ const TextEditor = ({ learningContextId, }); const editorContent = newContent || initialContent; + let staticRootUrl; + if (isLibrary) { + staticRootUrl = `${getConfig().STUDIO_BASE_URL }/library_assets/blocks/${ blockId }/`; + } if (!refReady) { return null; } @@ -65,6 +71,7 @@ const TextEditor = ({ images, isLibrary, learningContextId, + staticRootUrl, }} /> ); @@ -107,6 +114,7 @@ TextEditor.propTypes = { blockValue: PropTypes.shape({ data: PropTypes.shape({ data: PropTypes.string }), }), + blockId: PropTypes.string, blockFailed: PropTypes.bool.isRequired, initializeEditor: PropTypes.func.isRequired, showRawEditor: PropTypes.bool.isRequired, @@ -121,6 +129,7 @@ TextEditor.propTypes = { export const mapStateToProps = (state) => ({ blockValue: selectors.app.blockValue(state), blockFailed: selectors.requests.isFailed(state, { requestKey: RequestKeys.fetchBlock }), + blockId: selectors.app.blockId(state), showRawEditor: selectors.app.showRawEditor(state), blockFinished: selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchBlock }), learningContextId: selectors.app.learningContextId(state), diff --git a/src/editors/data/redux/app/reducer.js b/src/editors/data/redux/app/reducer.js index 1951983f4a..faf2848bb5 100644 --- a/src/editors/data/redux/app/reducer.js +++ b/src/editors/data/redux/app/reducer.js @@ -42,6 +42,7 @@ const app = createSlice({ blockValue: payload, blockTitle: payload.data.display_name, }), + setBlockId: (state, { payload }) => ({...state, blockId: payload,}), setStudioView: (state, { payload }) => ({ ...state, studioView: payload }), setBlockContent: (state, { payload }) => ({ ...state, blockContent: payload }), setBlockTitle: (state, { payload }) => ({ ...state, blockTitle: payload }), diff --git a/src/editors/sharedComponents/TinyMceWidget/__snapshots__/index.test.jsx.snap b/src/editors/sharedComponents/TinyMceWidget/__snapshots__/index.test.jsx.snap index a0bf40260a..01e328f8cc 100644 --- a/src/editors/sharedComponents/TinyMceWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/sharedComponents/TinyMceWidget/__snapshots__/index.test.jsx.snap @@ -44,6 +44,7 @@ exports[`TinyMceWidget snapshots ImageUploadModal is not rendered 1`] = ` "selection": "hooks.selectedImage.selection", "setEditorRef": undefined, "setSelection": [MockFunction hooks.selectedImage.setSelection], + "staticRootUrl": undefined, "studioEndpointUrl": "sOmEoThERvaLue.cOm", "updateContent": [Function], } @@ -112,6 +113,7 @@ exports[`TinyMceWidget snapshots SourcecodeModal is not rendered 1`] = ` "selection": "hooks.selectedImage.selection", "setEditorRef": undefined, "setSelection": [MockFunction hooks.selectedImage.setSelection], + "staticRootUrl": undefined, "studioEndpointUrl": "sOmEoThERvaLue.cOm", "updateContent": [Function], } @@ -191,6 +193,7 @@ exports[`TinyMceWidget snapshots renders as expected with default behavior 1`] = "selection": "hooks.selectedImage.selection", "setEditorRef": undefined, "setSelection": [MockFunction hooks.selectedImage.setSelection], + "staticRootUrl": undefined, "studioEndpointUrl": "sOmEoThERvaLue.cOm", "updateContent": [Function], } diff --git a/src/editors/sharedComponents/TinyMceWidget/hooks.js b/src/editors/sharedComponents/TinyMceWidget/hooks.js index 6fe5b313e5..9f959f1638 100644 --- a/src/editors/sharedComponents/TinyMceWidget/hooks.js +++ b/src/editors/sharedComponents/TinyMceWidget/hooks.js @@ -84,20 +84,6 @@ export const replaceStaticWithAsset = ({ editorType, lmsEndpointUrl, }) => { - if (isLibraryKey(learningContextId)) { - // This function doesn't currently know how to deal with Library assets. - // Libraries don't mangle the path into an asset key–it might be sufficient - // to remove the initial "/" in a "/static/images/foo.png" link, and then - // set the base URL to the correct ComponentVersion base. If we let this - // function try to deal with Library assets, it would convert them in such a - // way that it wouldn't convert back later on, and we'd end up storing the - // incorrect OLX and breaking the display from that point forward. - // - // So until we handle it better, just disable static asset URL substitutions - // when dealing with Library content. - return false; - } - let content = initialContent; let hasChanges = false; const srcs = content.split(/(src="|src="|href="|href=")/g).filter( @@ -116,7 +102,15 @@ export const replaceStaticWithAsset = ({ // assets in expandable text areas do not support relative urls so all assets must have the lms // endpoint prepended to the relative url - if (editorType === 'expandable') { + if (isLibraryKey(learningContextId)) { + // We are removing the initial "/" in a "/static/foo.png" link, and then + // set the base URL to an endpoint serving the draft version of an asset by + // its path. + /* istanbul ignore next */ + if (isStatic) { + staticFullUrl = assetSrc.substring(1); + } + } else if (editorType === 'expandable') { if (isCorrectAssetFormat) { staticFullUrl = `${lmsEndpointUrl}${assetSrc}`; } else { @@ -261,9 +255,12 @@ export const editorConfig = ({ content, minHeight, learningContextId, + staticRootUrl, }) => { const lmsEndpointUrl = getConfig().LMS_BASE_URL; const studioEndpointUrl = getConfig().STUDIO_BASE_URL; + + const baseURL = staticRootUrl || lmsEndpointUrl; const { toolbar, config, @@ -290,7 +287,7 @@ export const editorConfig = ({ min_height: minHeight, contextmenu: 'link table', directionality: isLocaleRtl ? 'rtl' : 'ltr', - document_base_url: lmsEndpointUrl, + document_base_url: baseURL, imagetools_cors_hosts: [removeProtocolFromUrl(lmsEndpointUrl), removeProtocolFromUrl(studioEndpointUrl)], imagetools_toolbar: imageToolbar, formats: { label: { inline: 'label' } }, @@ -436,6 +433,17 @@ export const setAssetToStaticUrl = ({ editorValue, lmsEndpointUrl }) => { const updatedContent = content.replace(currentSrc, portableUrl); content = updatedContent; }); + + /* istanbul ignore next */ + assetSrcs.filter(src => src.startsWith('static/')).forEach(src => { + // Before storing assets we make sure that library static assets points again to + // `/static/dummy.jpg` instead of using the relative url `static/dummy.jpg` + const nameFromEditorSrc = parseAssetName(src); + const portableUrl = `/${ nameFromEditorSrc}`; + const currentSrc = src.substring(0, src.search(/("|")/)); + const updatedContent = content.replace(currentSrc, portableUrl); + content = updatedContent; + }); return content; }; diff --git a/src/editors/sharedComponents/TinyMceWidget/index.jsx b/src/editors/sharedComponents/TinyMceWidget/index.jsx index b311e863a0..9294cf0bda 100644 --- a/src/editors/sharedComponents/TinyMceWidget/index.jsx +++ b/src/editors/sharedComponents/TinyMceWidget/index.jsx @@ -44,6 +44,7 @@ const TinyMceWidget = ({ images, isLibrary, onChange, + staticRootUrl, ...editorConfig }) => { const { isImgOpen, openImgModal, closeImgModal } = hooks.imgModalToggle(); @@ -85,6 +86,7 @@ const TinyMceWidget = ({ learningContextId, images: imagesRef, editorContentHtml, + staticRootUrl, ...imageSelection, ...editorConfig, })