fix: display image preview in libraries editor (#1403)

Prior to this commit, the TinyMCE editor image preview only worked in
courses, and did not work for content libraries.
This commit is contained in:
Cristhian Garcia
2024-10-24 08:39:37 -05:00
committed by GitHub
parent a8aa495542
commit e1ce3eb484
7 changed files with 41 additions and 16 deletions

View File

@@ -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),

View File

@@ -59,6 +59,7 @@ jest.mock('../../data/redux', () => ({
showRawEditor: jest.fn(state => ({ showRawEditor: state })),
images: jest.fn(state => ({ images: state })),
isLibrary: jest.fn(state => ({ isLibrary: state })),
blockId: jest.fn(state => ({ blockId: state })),
learningContextId: jest.fn(state => ({ learningContextId: state })),
},
requests: {

View File

@@ -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 }),

View File

@@ -45,6 +45,7 @@ describe('app reducer', () => {
['setUnitUrl', 'unitUrl'],
['setStudioView', 'studioView'],
['setBlockContent', 'blockContent'],
['setBlockId', 'blockId'],
['setBlockTitle', 'blockTitle'],
['setSaveResponse', 'saveResponse'],
['setVideos', 'videos'],

View File

@@ -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],
}

View File

@@ -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 keyit 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=&quot)/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;
};

View File

@@ -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,
})