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, }} />