/**
* [[include:plugins/iframe/README.md]]
* @packageDocumentation
* @module plugins/iframe
*/
import type { IJodit } from 'jodit/types';
import { css, defaultLanguage, attr, callPromise } from 'jodit/core/helpers/';
import { error } from 'jodit/core/helpers';
import { MODE_SOURCE } from 'jodit/core/constants';
import { pluginSystem } from 'jodit/core/global';
import './config';
/**
* Iframe plugin - use `iframe` instead of DIV in editor. It can be need when you want to attach custom styles in editor
* in backend of you system
*/
export function iframe(editor: IJodit): void {
const opt = editor.options;
editor.e
.on('afterSetMode', () => {
if (editor.isEditorMode()) {
editor.s.focus();
}
})
.on(
'generateDocumentStructure.iframe',
(__doc: Document | undefined, jodit: IJodit) => {
const doc =
__doc ||
(
(jodit.iframe as HTMLIFrameElement)
.contentWindow as Window
).document;
doc.open();
doc.write(
opt.iframeDoctype +
`` +
'
' +
`${opt.iframeTitle}` +
(opt.iframeBaseUrl
? ``
: '') +
'' +
'' +
''
);
doc.close();
if (opt.iframeCSSLinks) {
opt.iframeCSSLinks.forEach(href => {
const link = doc.createElement('link');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('href', href);
doc.head && doc.head.appendChild(link);
});
}
if (opt.iframeStyle) {
const style = doc.createElement('style');
style.innerHTML = opt.iframeStyle;
doc.head && doc.head.appendChild(style);
}
}
)
.on('createEditor', (): void | Promise | false => {
if (!opt.iframe) {
return;
}
const iframe = editor.c.element('iframe');
iframe.style.display = 'block';
iframe.src = 'about:blank';
iframe.className = 'jodit-wysiwyg_iframe';
iframe.setAttribute('allowtransparency', 'true');
iframe.setAttribute('tabindex', opt.tabIndex.toString());
iframe.setAttribute('frameborder', '0');
editor.workplace.appendChild(iframe);
editor.iframe = iframe;
const result = editor.e.fire(
'generateDocumentStructure.iframe',
null,
editor
);
const init = (): boolean => {
if (!editor.iframe) {
return false;
}
const doc = (editor.iframe.contentWindow as Window).document;
editor.editorWindow = editor.iframe.contentWindow as Window;
const docMode = opt.editHTMLDocumentMode;
const toggleEditable = (): void => {
attr(
doc.body,
'contenteditable',
(editor.getMode() !== MODE_SOURCE &&
!editor.getReadOnly()) ||
null
);
};
const clearMarkers = (html: string): string => {
const bodyReg = //im,
bodyMarker = '{%%BODY%%}',
body = bodyReg.exec(html);
if (body) {
// remove markers
html = html
.replace(bodyReg, bodyMarker)
.replace(/]*?)>(.*?)<\/span>/gim, '')
.replace(
/<span([^&]*?)>(.*?)<\/span>/gim,
''
)
.replace(
bodyMarker,
body[0]
.replace(
/(]+?)min-height["'\s]*:[\s"']*[0-9]+(px|%)/im,
'$1'
)
.replace(
/(]+?)([\s]*["'])?contenteditable["'\s]*=[\s"']*true["']?/im,
'$1'
)
.replace(
/<(style|script|span)[^>]+jodit[^>]+>.*?<\/\1>/g,
''
)
)
.replace(
/(class\s*=\s*)(['"])([^"']*)(jodit-wysiwyg|jodit)([^"']*\2)/g,
'$1$2$3$5'
)
.replace(/(<[^<]+?)\sclass="[\s]*"/gim, '$1')
.replace(/(<[^<]+?)\sstyle="[\s;]*"/gim, '$1')
.replace(/(<[^<]+?)\sdir="[\s]*"/gim, '$1');
}
return html;
};
if (docMode) {
const tag = editor.element.tagName;
if (tag !== 'TEXTAREA' && tag !== 'INPUT') {
throw error(
'If enable `editHTMLDocumentMode` - source element should be INPUT or TEXTAREA'
);
}
editor.e
.on('beforeGetNativeEditorValue', (): string =>
clearMarkers(
editor.o.iframeDoctype +
doc.documentElement.outerHTML
)
)
.on(
'beforeSetNativeEditorValue',
({ value }: { value: string }): boolean => {
if (editor.isLocked) {
return false;
}
if (/<(html|body)/i.test(value)) {
const old = doc.documentElement.outerHTML;
if (
clearMarkers(old) !==
clearMarkers(value)
) {
doc.open();
doc.write(
editor.o.iframeDoctype +
clearMarkers(value)
);
doc.close();
editor.editor = doc.body;
editor.e.fire(
'safeHTML',
editor.editor
);
toggleEditable();
editor.e.fire('prepareWYSIWYGEditor');
editor.e.stopPropagation(
'beforeSetNativeEditorValue'
);
}
} else {
doc.body.innerHTML = value;
}
return true;
},
{ top: true }
);
}
editor.editor = doc.body;
editor.e.on(
'afterSetMode afterInit afterAddPlace',
toggleEditable
);
if (opt.height === 'auto') {
doc.documentElement &&
(doc.documentElement.style.overflowY = 'hidden');
const resizeIframe = editor.async.throttle(() => {
if (
editor.editor &&
editor.iframe &&
opt.height === 'auto'
) {
const style = editor.ew.getComputedStyle(
editor.editor
),
marginOffset =
parseInt(style.marginTop || '0', 10) +
parseInt(style.marginBottom || '0', 10);
css(
editor.iframe,
'height',
editor.editor.offsetHeight + marginOffset
);
}
}, editor.defaultTimeout / 2);
editor.e
.on(
'change afterInit afterSetMode resize',
resizeIframe
)
.on(
[editor.iframe, editor.ew, doc.documentElement],
'load',
resizeIframe
)
.on(
doc,
'readystatechange DOMContentLoaded',
resizeIframe
);
if (typeof ResizeObserver === 'function') {
const resizeObserver = new ResizeObserver(resizeIframe);
resizeObserver.observe(doc.body);
editor.e.on('beforeDestruct', () => {
resizeObserver.unobserve(doc.body);
});
}
}
// throw events in our world
if (doc.documentElement) {
editor.e
.on(doc.documentElement, 'mousedown touchend', () => {
if (!editor.s.isFocused()) {
editor.s.focus();
if (editor.editor === doc.body) {
editor.s.setCursorIn(doc.body);
}
}
})
.on(
editor.ew,
'mousedown touchstart keydown keyup touchend click mouseup mousemove scroll',
(e: Event) => {
editor.events?.fire(editor.ow, e);
}
);
}
return false;
};
return callPromise(result, init);
});
}
pluginSystem.add('iframe', iframe);