feat: TinyMCE plugin insert iframe (#427)
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
function tinyMCEEmbedIframePlugin(editor) {
|
||||
function openInsertIframeModal() {
|
||||
const defaultConfig = {
|
||||
title: 'Insert iframe',
|
||||
body: {
|
||||
type: 'tabpanel',
|
||||
tabs: [
|
||||
{
|
||||
title: 'General',
|
||||
items: [
|
||||
{
|
||||
type: 'input',
|
||||
name: 'source',
|
||||
label: 'Source URL',
|
||||
multiline: false,
|
||||
autofocus: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'selectbox',
|
||||
name: 'sizeType',
|
||||
label: 'Size',
|
||||
items: [
|
||||
{ text: 'Inline Value', value: 'inline' },
|
||||
{ text: 'Big embed', value: 'big' },
|
||||
{ text: 'Small embed', value: 'small' },
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
type: 'sizeinput',
|
||||
name: 'size',
|
||||
label: 'Dimensions',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'longDescriptionURL',
|
||||
label: 'Long description URL',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'border',
|
||||
label: 'Show iframe border',
|
||||
text: 'Border',
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'scrollbar',
|
||||
label: 'Enable scrollbar',
|
||||
text: 'Scrollbar',
|
||||
checked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
type: 'cancel',
|
||||
name: 'cancel',
|
||||
text: 'Cancel',
|
||||
},
|
||||
{
|
||||
type: 'submit',
|
||||
name: 'save',
|
||||
text: 'Save',
|
||||
primary: true,
|
||||
},
|
||||
],
|
||||
onChange(api, field) {
|
||||
const { name } = field;
|
||||
const data = api.getData();
|
||||
const { sizeType, ...fields } = data;
|
||||
const isSizeTypeFiled = name === 'sizeType';
|
||||
const hasCustomSize = sizeType === 'inline';
|
||||
|
||||
if (!hasCustomSize && isSizeTypeFiled) {
|
||||
const {
|
||||
body: {
|
||||
tabs: [generalTab],
|
||||
},
|
||||
} = defaultConfig;
|
||||
|
||||
generalTab.items = generalTab.items.filter(
|
||||
(item) => item.type !== 'sizeinput',
|
||||
);
|
||||
|
||||
defaultConfig.initialData = { ...fields, sizeType };
|
||||
api.redial(defaultConfig);
|
||||
}
|
||||
|
||||
if (hasCustomSize && isSizeTypeFiled) {
|
||||
const {
|
||||
body: {
|
||||
tabs: [generalTab],
|
||||
},
|
||||
} = defaultConfig;
|
||||
|
||||
const hasSizeInput = generalTab.items.some((item) => item.name === 'size');
|
||||
|
||||
if (!hasSizeInput) {
|
||||
generalTab.items = [
|
||||
...generalTab.items,
|
||||
{
|
||||
type: 'sizeinput',
|
||||
name: 'size',
|
||||
label: 'Dimensions',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
defaultConfig.initialData = { ...fields, sizeType };
|
||||
api.redial(defaultConfig);
|
||||
}
|
||||
},
|
||||
onSubmit(api) {
|
||||
const data = api.getData();
|
||||
const sizeTypes = {
|
||||
small: {
|
||||
height: '100px',
|
||||
width: '100px',
|
||||
},
|
||||
big: {
|
||||
height: '800px',
|
||||
width: '800px',
|
||||
},
|
||||
};
|
||||
if (data.source) {
|
||||
const {
|
||||
size, sizeType, name, title, longDescriptionURL, border, scrollbar,
|
||||
} = data;
|
||||
const { width, height } = sizeTypes[sizeType] || { width: size.width, height: size.height };
|
||||
|
||||
const pxRegex = /^\d+px$/;
|
||||
const widthFormat = pxRegex.test(width) ? width : '300px';
|
||||
const heightFormat = pxRegex.test(height) ? height : '300px';
|
||||
const hasScroll = scrollbar ? 'yes' : 'no';
|
||||
|
||||
let iframeCode = `<iframe src="${data.source}" width="${widthFormat}" height="${heightFormat}" scrolling="${hasScroll}"`;
|
||||
|
||||
if (name) {
|
||||
iframeCode += ` name="${name}"`;
|
||||
}
|
||||
|
||||
if (title) {
|
||||
iframeCode += ` title="${title}"`;
|
||||
}
|
||||
|
||||
if (longDescriptionURL) {
|
||||
iframeCode += ` longdesc="${longDescriptionURL}"`;
|
||||
}
|
||||
|
||||
if (!border) {
|
||||
iframeCode += 'frameborder="0"';
|
||||
}
|
||||
|
||||
iframeCode += '></iframe>';
|
||||
|
||||
iframeCode = `<div class="tiny-pageembed" style="width: ${widthFormat}; height: ${heightFormat}">`
|
||||
+ `${iframeCode}`
|
||||
+ '</div>';
|
||||
|
||||
editor.insertContent(iframeCode);
|
||||
}
|
||||
|
||||
api.close();
|
||||
},
|
||||
};
|
||||
|
||||
editor.windowManager.open(defaultConfig);
|
||||
}
|
||||
|
||||
// Register the button
|
||||
editor.ui.registry.addButton('embediframe', {
|
||||
text: 'Embed iframe',
|
||||
onAction: openInsertIframeModal,
|
||||
});
|
||||
}
|
||||
|
||||
((tinymce) => {
|
||||
if (tinymce) {
|
||||
tinymce.PluginManager.add('embediframe', tinyMCEEmbedIframePlugin);
|
||||
}
|
||||
})(window.tinymce);
|
||||
|
||||
export default tinyMCEEmbedIframePlugin;
|
||||
@@ -0,0 +1,408 @@
|
||||
import tinyMCEEmbedIframePlugin from './embedIframePlugin';
|
||||
|
||||
const editorMock = {
|
||||
windowManager: {
|
||||
open: jest.fn(),
|
||||
},
|
||||
insertContent: jest.fn(),
|
||||
ui: {
|
||||
registry: {
|
||||
addButton: jest.fn(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('TinyMCE Embed IFrame Plugin', () => {
|
||||
const pluginConfig = {
|
||||
title: 'Insert iframe',
|
||||
body: {
|
||||
type: 'tabpanel',
|
||||
tabs: [
|
||||
{
|
||||
title: 'General',
|
||||
items: [
|
||||
{
|
||||
type: 'input',
|
||||
name: 'source',
|
||||
label: 'Source URL',
|
||||
multiline: false,
|
||||
autofocus: true,
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'selectbox',
|
||||
name: 'sizeType',
|
||||
label: 'Size',
|
||||
items: [
|
||||
{ text: 'Inline Value', value: 'inline' },
|
||||
{ text: 'Big embed', value: 'big' },
|
||||
{ text: 'Small embed', value: 'small' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'sizeinput',
|
||||
name: 'size',
|
||||
label: 'Dimensions',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Advanced',
|
||||
items: [
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
label: 'Name',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'longDescriptionURL',
|
||||
label: 'Long description URL',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'border',
|
||||
label: 'Show iframe border',
|
||||
text: 'Border',
|
||||
checked: false,
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'scrollbar',
|
||||
label: 'Enable scrollbar',
|
||||
text: 'Scrollbar',
|
||||
checked: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
type: 'cancel',
|
||||
name: 'cancel',
|
||||
text: 'Cancel',
|
||||
},
|
||||
{
|
||||
type: 'submit',
|
||||
name: 'save',
|
||||
text: 'Save',
|
||||
primary: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('opens insert iframe modal on button action', () => {
|
||||
// Invoke the plugin
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
expect(editorMock.windowManager.open).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('opens insert iframe modal on button action validate onSubmit and OnChange function', () => {
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
expect(editorMock.windowManager.open).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onSubmit: expect.any(Function),
|
||||
onChange: expect.any(Function),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('opens insert iframe modal on button action validate title', () => {
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
expect(editorMock.windowManager.open).toHaveBeenCalled();
|
||||
expect(editorMock.windowManager.open).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
title: pluginConfig.title,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('opens insert iframe modal on button action validate buttons', () => {
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
expect(editorMock.windowManager.open).toHaveBeenCalled();
|
||||
expect(editorMock.windowManager.open).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
buttons: pluginConfig.buttons,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('opens insert iframe modal on button action validate tabs', () => {
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
const [generalTab, advancedTab] = pluginConfig.body.tabs;
|
||||
|
||||
expect(editorMock.windowManager.open).toHaveBeenCalled();
|
||||
expect(editorMock.windowManager.open).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
body: { type: 'tabpanel', tabs: [generalTab, advancedTab] },
|
||||
}),
|
||||
);
|
||||
});
|
||||
test('tests onChange function in plugin', () => {
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
|
||||
// Access the onChange function from the opened configuration
|
||||
const onChangeFunction = editorMock.windowManager.open.mock.calls[0][0].onChange;
|
||||
|
||||
// Mock API and field for onChange
|
||||
const apiMock = {
|
||||
getData: jest.fn(() => ({ sizeType: 'big' })),
|
||||
redial: jest.fn(),
|
||||
};
|
||||
const field = {
|
||||
name: 'sizeType',
|
||||
};
|
||||
|
||||
// Simulate calling the onChange function
|
||||
onChangeFunction(apiMock, field);
|
||||
|
||||
expect(apiMock.getData).toHaveBeenCalled();
|
||||
expect(apiMock.redial).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('modifies generalTab items when sizeType is not inline', () => {
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
|
||||
const onChangeFunction = editorMock.windowManager.open.mock.calls[0][0].onChange;
|
||||
|
||||
const apiMock = {
|
||||
getData: jest.fn(() => ({ sizeType: 'big' })),
|
||||
redial: jest.fn(),
|
||||
};
|
||||
const field = {
|
||||
name: 'sizeType',
|
||||
};
|
||||
|
||||
onChangeFunction(apiMock, field);
|
||||
|
||||
const [generalTab, advancedTab] = pluginConfig.body.tabs;
|
||||
const generalTabExpected = generalTab.items.filter(
|
||||
(item) => item.type !== 'sizeinput',
|
||||
);
|
||||
|
||||
const expectedTabs = [
|
||||
{ title: generalTab.title, items: generalTabExpected, type: generalTab.type },
|
||||
advancedTab,
|
||||
];
|
||||
|
||||
const expectedBody = {
|
||||
type: pluginConfig.body.type,
|
||||
tabs: expectedTabs,
|
||||
};
|
||||
|
||||
expect(apiMock.redial).toHaveBeenCalledWith(expect.objectContaining({
|
||||
body: expectedBody,
|
||||
}));
|
||||
});
|
||||
|
||||
test('adds sizeinput to generalTab items when sizeType is inline', () => {
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
|
||||
const onChangeFunction = editorMock.windowManager.open.mock.calls[0][0].onChange;
|
||||
|
||||
const apiMock = {
|
||||
getData: jest.fn(() => ({ sizeType: 'inline' })),
|
||||
redial: jest.fn(),
|
||||
};
|
||||
const field = {
|
||||
name: 'sizeType',
|
||||
};
|
||||
|
||||
onChangeFunction(apiMock, field);
|
||||
|
||||
const [generalTab, advancedTab] = pluginConfig.body.tabs;
|
||||
|
||||
expect(apiMock.redial).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
body: { type: 'tabpanel', tabs: [generalTab, advancedTab] },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('tests onSubmit function in plugin', () => {
|
||||
const dataMock = {
|
||||
source: 'https://www.example.com',
|
||||
sizeType: 'big',
|
||||
};
|
||||
const apiMock = {
|
||||
getData: jest.fn(() => dataMock),
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
|
||||
const onSubmitFunction = editorMock.windowManager.open.mock.calls[0][0].onSubmit;
|
||||
onSubmitFunction(apiMock);
|
||||
|
||||
expect(apiMock.getData).toHaveBeenCalled();
|
||||
expect(editorMock.insertContent).toHaveBeenCalled();
|
||||
expect(apiMock.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('tests onSubmit function in plugin advanced properties', () => {
|
||||
const dataMock = {
|
||||
source: 'https://www.example.com',
|
||||
sizeType: 'big',
|
||||
name: 'iframeName',
|
||||
title: 'iframeTitle',
|
||||
longDescriptionURL: 'https://example.com/description',
|
||||
border: true,
|
||||
scrollbar: true,
|
||||
};
|
||||
const apiMock = {
|
||||
getData: jest.fn(() => dataMock),
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
|
||||
const onSubmitFunction = editorMock.windowManager.open.mock.calls[0][0].onSubmit;
|
||||
onSubmitFunction(apiMock);
|
||||
|
||||
expect(apiMock.getData).toHaveBeenCalled();
|
||||
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining('width="800px"'));
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining('height="800px"'));
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining(`name="${dataMock.name}"`));
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining(`title="${dataMock.title}"`));
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining(`longdesc="${dataMock.longDescriptionURL}"`));
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining('scrolling="yes"'));
|
||||
|
||||
expect(apiMock.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
describe('tests onSubmit function in plugin sizeType', () => {
|
||||
test('tests onSubmit function in plugin with sizeType big', () => {
|
||||
const dataMock = {
|
||||
source: 'https://www.example.com',
|
||||
sizeType: 'big',
|
||||
};
|
||||
|
||||
const apiMock = {
|
||||
getData: jest.fn(() => dataMock),
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
|
||||
const onSubmitFunction = editorMock.windowManager.open.mock.calls[0][0].onSubmit;
|
||||
onSubmitFunction(apiMock);
|
||||
|
||||
expect(apiMock.getData).toHaveBeenCalled();
|
||||
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining('width="800px"'));
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining('height="800px"'));
|
||||
|
||||
expect(apiMock.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('tests onSubmit function in plugin with sizeType small', () => {
|
||||
const dataMock = {
|
||||
source: 'https://www.example.com',
|
||||
sizeType: 'small',
|
||||
};
|
||||
|
||||
const apiMock = {
|
||||
getData: jest.fn(() => dataMock),
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
|
||||
const onSubmitFunction = editorMock.windowManager.open.mock.calls[0][0].onSubmit;
|
||||
onSubmitFunction(apiMock);
|
||||
|
||||
expect(apiMock.getData).toHaveBeenCalled();
|
||||
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining('width="100px"'));
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining('height="100px"'));
|
||||
expect(apiMock.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('tests onSubmit function in plugin with custom sizeType', () => {
|
||||
const dataMock = {
|
||||
source: 'https://www.example.com',
|
||||
sizeType: 'inline',
|
||||
size: {
|
||||
width: '500px',
|
||||
height: '700px',
|
||||
},
|
||||
};
|
||||
|
||||
const apiMock = {
|
||||
getData: jest.fn(() => dataMock),
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
|
||||
const onSubmitFunction = editorMock.windowManager.open.mock.calls[0][0].onSubmit;
|
||||
onSubmitFunction(apiMock);
|
||||
|
||||
expect(apiMock.getData).toHaveBeenCalled();
|
||||
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining('width="500px"'));
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining('height="700px"'));
|
||||
expect(apiMock.close).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('tests onSubmit function in plugin with custom sizeType invalid values', () => {
|
||||
const dataMock = {
|
||||
source: 'https://www.example.com',
|
||||
sizeType: 'inline',
|
||||
size: {
|
||||
width: 'test',
|
||||
height: 'test',
|
||||
},
|
||||
};
|
||||
|
||||
const apiMock = {
|
||||
getData: jest.fn(() => dataMock),
|
||||
close: jest.fn(),
|
||||
};
|
||||
|
||||
tinyMCEEmbedIframePlugin(editorMock);
|
||||
editorMock.ui.registry.addButton.mock.calls[0][1].onAction();
|
||||
|
||||
const onSubmitFunction = editorMock.windowManager.open.mock.calls[0][0].onSubmit;
|
||||
onSubmitFunction(apiMock);
|
||||
|
||||
expect(apiMock.getData).toHaveBeenCalled();
|
||||
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining('width="300px"'));
|
||||
expect(editorMock.insertContent).toHaveBeenCalledWith(expect.stringContaining('height="300px"'));
|
||||
expect(apiMock.close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,7 @@ import { selectors } from '../../data/redux';
|
||||
import ImageUploadModal from '../ImageUploadModal';
|
||||
import SourceCodeModal from '../SourceCodeModal';
|
||||
import * as hooks from './hooks';
|
||||
import './customTinyMcePlugins/embedIframePlugin';
|
||||
|
||||
const editorConfigDefaultProps = {
|
||||
setEditorRef: undefined,
|
||||
|
||||
@@ -33,6 +33,7 @@ const pluginConfig = ({ isLibrary, placeholder, editorType }) => {
|
||||
quickToolbar,
|
||||
plugins.a11ychecker,
|
||||
plugins.powerpaste,
|
||||
plugins.embediframe,
|
||||
].join(' '),
|
||||
menubar: false,
|
||||
toolbar: toolbar ? mapToolbars([
|
||||
@@ -54,7 +55,7 @@ const pluginConfig = ({ isLibrary, placeholder, editorType }) => {
|
||||
],
|
||||
[imageUploadButton, buttons.link, buttons.unlink, buttons.blockQuote, buttons.codeBlock],
|
||||
[buttons.table, buttons.emoticons, buttons.charmap, buttons.hr],
|
||||
[buttons.removeFormat, codeButton, buttons.a11ycheck],
|
||||
[buttons.removeFormat, codeButton, buttons.a11ycheck, buttons.embediframe],
|
||||
]) : false,
|
||||
imageToolbar: mapToolbars([
|
||||
// [buttons.rotate.left, buttons.rotate.right],
|
||||
|
||||
Reference in New Issue
Block a user