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
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ export const EditorPage = ({
|
||||
courseId,
|
||||
blockType,
|
||||
blockId,
|
||||
lmsEndpointUrl,
|
||||
studioEndpointUrl,
|
||||
}) => (
|
||||
<Provider store={store}>
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -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', () => ({
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
</Provider>
|
||||
|
||||
@@ -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 }),
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 (
|
||||
<div className="gallery">
|
||||
@@ -36,6 +37,7 @@ export const EditorGallery = () => {
|
||||
blockType,
|
||||
courseId,
|
||||
studioEndpointUrl,
|
||||
lmsEndpointUrl,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user