From 33b2c6a660ade9ab4c457598396febdd5b2be3b6 Mon Sep 17 00:00:00 2001
From: connorhaugh <49422820+connorhaugh@users.noreply.github.com>
Date: Fri, 8 Apr 2022 13:23:42 -0400
Subject: [PATCH] feat: image context toolbar (#55)
Note: this removes the crop and rotate functionality from the toolbar, as it requires an image_proxy server or tinymce cloud. https://www.tiny.cloud/docs-4x/plugins/imagetools/#imagetools_proxy. We also need to create a follow up ticket to handle right click behavior.
This is that follow up ticket
---
src/editors/Editor.jsx | 4 ++++
src/editors/Editor.test.jsx | 1 +
src/editors/EditorPage.jsx | 4 ++++
src/editors/EditorPage.test.jsx | 1 +
src/editors/__snapshots__/EditorPage.test.jsx.snap | 1 +
src/editors/containers/TextEditor/TextEditor.jsx | 5 +++++
src/editors/containers/TextEditor/TextEditor.test.jsx | 7 +++++++
.../TextEditor/__snapshots__/TextEditor.test.jsx.snap | 2 ++
src/editors/containers/TextEditor/hooks.js | 5 ++---
src/editors/containers/TextEditor/hooks.test.jsx | 4 +++-
src/editors/data/redux/app/reducer.js | 2 ++
src/editors/data/redux/app/reducer.test.js | 1 +
src/editors/data/redux/app/selectors.js | 1 +
www/src/Gallery.jsx | 2 ++
14 files changed, 36 insertions(+), 4 deletions(-)
diff --git a/src/editors/Editor.jsx b/src/editors/Editor.jsx
index a456eadc0..d744fcff3 100644
--- a/src/editors/Editor.jsx
+++ b/src/editors/Editor.jsx
@@ -25,6 +25,7 @@ export const Editor = ({
courseId,
blockType,
blockId,
+ lmsEndpointUrl,
studioEndpointUrl,
// redux
initialize,
@@ -35,6 +36,7 @@ export const Editor = ({
blockId,
blockType,
courseId,
+ lmsEndpointUrl,
studioEndpointUrl,
},
});
@@ -67,6 +69,7 @@ export const Editor = ({
Editor.defaultProps = {
courseId: null,
blockId: null,
+ lmsEndpointUrl: null,
studioEndpointUrl: null,
};
@@ -74,6 +77,7 @@ Editor.propTypes = {
courseId: PropTypes.string,
blockType: PropTypes.string.isRequired,
blockId: PropTypes.string,
+ lmsEndpointUrl: PropTypes.string,
studioEndpointUrl: PropTypes.string,
// redux
initialize: PropTypes.func.isRequired,
diff --git a/src/editors/Editor.test.jsx b/src/editors/Editor.test.jsx
index 3bc349f5b..1362a3554 100644
--- a/src/editors/Editor.test.jsx
+++ b/src/editors/Editor.test.jsx
@@ -20,6 +20,7 @@ const props = {
courseId: 'course-v1:edX+DemoX+Demo_Course',
blockId: 'block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4',
studioEndpointUrl: 'fakeurl.com',
+ lmsEndpointUrl: 'evenfakerurl.com',
initialize: jest.fn(),
};
diff --git a/src/editors/EditorPage.jsx b/src/editors/EditorPage.jsx
index 0298679e6..c5c637acc 100644
--- a/src/editors/EditorPage.jsx
+++ b/src/editors/EditorPage.jsx
@@ -9,6 +9,7 @@ export const EditorPage = ({
courseId,
blockType,
blockId,
+ lmsEndpointUrl,
studioEndpointUrl,
}) => (
@@ -17,6 +18,7 @@ export const EditorPage = ({
courseId,
blockType,
blockId,
+ lmsEndpointUrl,
studioEndpointUrl,
}}
/>
@@ -25,6 +27,7 @@ export const EditorPage = ({
EditorPage.defaultProps = {
courseId: null,
blockId: null,
+ lmsEndpointUrl: null,
studioEndpointUrl: null,
};
@@ -32,6 +35,7 @@ EditorPage.propTypes = {
courseId: PropTypes.string,
blockType: PropTypes.string.isRequired,
blockId: PropTypes.string,
+ lmsEndpointUrl: PropTypes.string,
studioEndpointUrl: PropTypes.string,
};
diff --git a/src/editors/EditorPage.test.jsx b/src/editors/EditorPage.test.jsx
index 2cf56dcf5..ec2ee90f9 100644
--- a/src/editors/EditorPage.test.jsx
+++ b/src/editors/EditorPage.test.jsx
@@ -6,6 +6,7 @@ const props = {
courseId: 'course-v1:edX+DemoX+Demo_Course',
blockType: 'html',
blockId: 'block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4',
+ lmsEndpointUrl: 'evenfakerurl.com',
studioEndpointUrl: 'fakeurl.com',
};
jest.mock('react-redux', () => ({
diff --git a/src/editors/__snapshots__/EditorPage.test.jsx.snap b/src/editors/__snapshots__/EditorPage.test.jsx.snap
index ad05865d2..508c08317 100644
--- a/src/editors/__snapshots__/EditorPage.test.jsx.snap
+++ b/src/editors/__snapshots__/EditorPage.test.jsx.snap
@@ -16,6 +16,7 @@ exports[`Editor Page snapshots rendering correctly with expected Input 1`] = `
blockId="block-v1:edX+DemoX+Demo_Course+type@html+block@030e35c4756a4ddc8d40b95fbbfff4d4"
blockType="html"
courseId="course-v1:edX+DemoX+Demo_Course"
+ lmsEndpointUrl="evenfakerurl.com"
studioEndpointUrl="fakeurl.com"
/>
diff --git a/src/editors/containers/TextEditor/TextEditor.jsx b/src/editors/containers/TextEditor/TextEditor.jsx
index e774502c3..9deb00bb5 100644
--- a/src/editors/containers/TextEditor/TextEditor.jsx
+++ b/src/editors/containers/TextEditor/TextEditor.jsx
@@ -40,6 +40,7 @@ export const TextEditor = ({
editorRef,
// redux
blockValue,
+ lmsEndpointUrl,
blockFailed,
blockFinished,
initializeEditor,
@@ -78,6 +79,7 @@ export const TextEditor = ({
blockValue,
openModal,
initializeEditor,
+ lmsEndpointUrl,
setSelection: imageSelection.setSelection,
clearSelection: imageSelection.clearSelection,
})}
@@ -89,6 +91,7 @@ export const TextEditor = ({
TextEditor.defaultProps = {
blockValue: null,
editorRef: null,
+ lmsEndpointUrl: null,
};
TextEditor.propTypes = {
editorRef: PropTypes.oneOfType([
@@ -100,6 +103,7 @@ TextEditor.propTypes = {
blockValue: PropTypes.shape({
data: PropTypes.shape({ data: PropTypes.string }),
}),
+ lmsEndpointUrl: PropTypes.string,
blockFailed: PropTypes.bool.isRequired,
blockFinished: PropTypes.bool.isRequired,
initializeEditor: PropTypes.func.isRequired,
@@ -109,6 +113,7 @@ TextEditor.propTypes = {
export const mapStateToProps = (state) => ({
blockValue: selectors.app.blockValue(state),
+ lmsEndpointUrl: selectors.app.lmsEndpointUrl(state),
blockFailed: selectors.requests.isFailed(state, { requestKey: RequestKeys.fetchBlock }),
blockFinished: selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchBlock }),
});
diff --git a/src/editors/containers/TextEditor/TextEditor.test.jsx b/src/editors/containers/TextEditor/TextEditor.test.jsx
index af56d7cea..ed50c653c 100644
--- a/src/editors/containers/TextEditor/TextEditor.test.jsx
+++ b/src/editors/containers/TextEditor/TextEditor.test.jsx
@@ -53,6 +53,7 @@ jest.mock('../../data/redux', () => ({
selectors: {
app: {
blockValue: jest.fn(state => ({ blockValue: state })),
+ lmsEndpointUrl: jest.fn(state => ({ lmsEndpointUrl: state })),
},
requests: {
isFailed: jest.fn((state, params) => ({ isFailed: { state, params } })),
@@ -67,6 +68,7 @@ describe('TextEditor', () => {
editorRef: { current: { value: 'something' } },
// redux
blockValue: { data: { some: 'eDiTablE Text' } },
+ lmsEndpointUrl: 'sOmEvaLue.cOm',
blockFailed: false,
blockFinished: true,
initializeEditor: jest.fn().mockName('args.intializeEditor'),
@@ -96,6 +98,11 @@ describe('TextEditor', () => {
mapStateToProps(testState).blockValue,
).toEqual(selectors.app.blockValue(testState));
});
+ test('lmsEndpointUrl from app.lmsEndpointUrl', () => {
+ expect(
+ mapStateToProps(testState).lmsEndpointUrl,
+ ).toEqual(selectors.app.lmsEndpointUrl(testState));
+ });
test('blockFailed from requests.isFailed', () => {
expect(
mapStateToProps(testState).blockFailed,
diff --git a/src/editors/containers/TextEditor/__snapshots__/TextEditor.test.jsx.snap b/src/editors/containers/TextEditor/__snapshots__/TextEditor.test.jsx.snap
index 4f57cd2e4..0a2298a80 100644
--- a/src/editors/containers/TextEditor/__snapshots__/TextEditor.test.jsx.snap
+++ b/src/editors/containers/TextEditor/__snapshots__/TextEditor.test.jsx.snap
@@ -38,6 +38,7 @@ exports[`TextEditor snapshots block failed to load, Toast is shown 1`] = `
},
"clearSelection": [MockFunction hooks.selectedImage.clearSelection],
"initializeEditor": [MockFunction args.intializeEditor],
+ "lmsEndpointUrl": "sOmEvaLue.cOm",
"openModal": [MockFunction modal.openModal],
"setEditorRef": [MockFunction args.setEditorRef],
"setSelection": [MockFunction hooks.selectedImage.setSelection],
@@ -125,6 +126,7 @@ exports[`TextEditor snapshots renders as expected with default behavior 1`] = `
},
"clearSelection": [MockFunction hooks.selectedImage.clearSelection],
"initializeEditor": [MockFunction args.intializeEditor],
+ "lmsEndpointUrl": "sOmEvaLue.cOm",
"openModal": [MockFunction modal.openModal],
"setEditorRef": [MockFunction args.setEditorRef],
"setSelection": [MockFunction hooks.selectedImage.setSelection],
diff --git a/src/editors/containers/TextEditor/hooks.js b/src/editors/containers/TextEditor/hooks.js
index 1b8d936b4..26daa04e9 100644
--- a/src/editors/containers/TextEditor/hooks.js
+++ b/src/editors/containers/TextEditor/hooks.js
@@ -68,8 +68,6 @@ export const pluginConfig = {
code: 'code',
}),
imageToolbar: StrictDict({
- rotate: 'rotateleft rotateright',
- flip: 'flipv fliph',
editImageSettings: 'editimagesettings',
}),
};
@@ -86,6 +84,7 @@ export const editorConfig = ({
openModal,
initializeEditor,
setSelection,
+ lmsEndpointUrl,
}) => ({
onInit: (evt, editor) => module.initializeEditorRef(setEditorRef, initializeEditor)(editor),
initialValue: blockValue ? blockValue.data.data : '',
@@ -95,7 +94,7 @@ export const editorConfig = ({
menubar: false,
toolbar: module.getConfig('toolbar'),
imagetools_toolbar: module.getConfig('imageToolbar'),
- imagetools_cors_hosts: ['courses.edx.org'],
+ imagetools_cors_hosts: [lmsEndpointUrl], // as image assets come from lms, we need to whitelist it.
height: '100%',
min_height: 500,
branding: false,
diff --git a/src/editors/containers/TextEditor/hooks.test.jsx b/src/editors/containers/TextEditor/hooks.test.jsx
index ced7c0f6f..2563188cc 100644
--- a/src/editors/containers/TextEditor/hooks.test.jsx
+++ b/src/editors/containers/TextEditor/hooks.test.jsx
@@ -88,11 +88,13 @@ describe('TextEditor hooks', () => {
});
describe('editorConfig', () => {
const blockvalue = null;
+ const lmsEndpointUrl = 'sOmEuRL.cOm';
const props = {
setEditorRef: jest.fn(),
blockValue: blockvalue,
openModal: jest.fn(),
initializeEditor: jest.fn(),
+ lmsEndpointUrl,
};
let output;
test('It creates an onInit which calls initializeEditor and setEditorRef', () => {
@@ -132,7 +134,7 @@ describe('TextEditor hooks', () => {
value => expect(output.init.imagetools_toolbar.includes(value)).toBe(true),
);
expect(output.init.menubar).toBe(false);
- expect(output.init.imagetools_cors_hosts).toMatchObject(['courses.edx.org']);
+ expect(output.init.imagetools_cors_hosts).toMatchObject([lmsEndpointUrl]);
expect(output.init.height).toBe('100%');
expect(output.init.min_height).toBe(500);
expect(output.init.branding).toBe(false);
diff --git a/src/editors/data/redux/app/reducer.js b/src/editors/data/redux/app/reducer.js
index 478de3557..21312811c 100644
--- a/src/editors/data/redux/app/reducer.js
+++ b/src/editors/data/redux/app/reducer.js
@@ -14,6 +14,7 @@ const initialState = {
courseId: null,
editorInitialized: false,
studioEndpointUrl: null,
+ lmsEndpointUrl: null,
};
// eslint-disable-next-line no-unused-vars
@@ -24,6 +25,7 @@ const app = createSlice({
initialize: (state, { payload }) => ({
...state,
studioEndpointUrl: payload.studioEndpointUrl,
+ lmsEndpointUrl: payload.lmsEndpointUrl,
blockId: payload.blockId,
courseId: payload.courseId,
blockType: payload.blockType,
diff --git a/src/editors/data/redux/app/reducer.test.js b/src/editors/data/redux/app/reducer.test.js
index 5344aebbf..ccb65f116 100644
--- a/src/editors/data/redux/app/reducer.test.js
+++ b/src/editors/data/redux/app/reducer.test.js
@@ -17,6 +17,7 @@ describe('app reducer', () => {
it('loads initial input fields into the store', () => {
const data = {
studioEndpointUrl: 'testURL',
+ lmsEndpointUrl: 'sOmEOtherTestuRl',
blockId: 'anID',
courseId: 'OTHERid',
blockType: 'someTYPE',
diff --git a/src/editors/data/redux/app/selectors.js b/src/editors/data/redux/app/selectors.js
index 18425939f..ce7e4f55a 100644
--- a/src/editors/data/redux/app/selectors.js
+++ b/src/editors/data/redux/app/selectors.js
@@ -17,6 +17,7 @@ export const simpleSelectors = {
courseId: mkSimpleSelector(app => app.courseId),
editorInitialized: mkSimpleSelector(app => app.editorInitialized),
saveResponse: mkSimpleSelector(app => app.saveResponse),
+ lmsEndpointUrl: mkSimpleSelector(app => app.lmsEndpointUrl),
studioEndpointUrl: mkSimpleSelector(app => app.studioEndpointUrl),
unitUrl: mkSimpleSelector(app => app.unitUrl),
blockTitle: mkSimpleSelector(app => app.blockTitle),
diff --git a/www/src/Gallery.jsx b/www/src/Gallery.jsx
index 714a8ec7e..292d66869 100644
--- a/www/src/Gallery.jsx
+++ b/www/src/Gallery.jsx
@@ -16,6 +16,7 @@ export const EditorGallery = () => {
}, {});
const courseId = 'fake-course-id';
const studioEndpointUrl = 'fake-studio-endpoint-url';
+ const lmsEndpointUrl = 'courses.edx.org'; // this is hardcoded because that is where the image data is from.
const handleChange = (e) => setBlockType(e.target.value);
return (
@@ -36,6 +37,7 @@ export const EditorGallery = () => {
blockType,
courseId,
studioEndpointUrl,
+ lmsEndpointUrl,
}}
/>