New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@tinymce/tinymce-react

Package Overview
Dependencies
Maintainers
2
Versions
390
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tinymce/tinymce-react - npm Package Compare versions

Comparing version 6.0.1-feature.20250302152522429.sha857c954 to 6.0.1-feature.20250303004124356.shab1f435a

39

lib/cjs/main/ts/components/Editor.d.ts
import * as React from 'react';
import type { TinyMCE, Editor as TinyMCEEditor } from 'tinymce';
import { IEvents } from '../Events';
import { ScriptItem } from '../ScriptLoader2';
import { IEditorPropTypes } from './EditorPropTypes';
import type { Editor as TinyMCEEditor, TinyMCE } from 'tinymce';
type OmitStringIndexSignature<T> = {

@@ -131,3 +132,37 @@ [K in keyof T as string extends K ? never : K]: T[K];

*/
export declare const Editor: React.FC<IAllProps>;
export declare class Editor extends React.Component<IAllProps> {
static propTypes: IEditorPropTypes;
static defaultProps: Partial<IAllProps>;
editor?: TinyMCEEditor;
private id;
private elementRef;
private inline;
private currentContent?;
private boundHandlers;
private rollbackTimer;
private valueCursor;
constructor(props: Partial<IAllProps>);
private get view();
componentDidUpdate(prevProps: Partial<IAllProps>): void;
componentDidMount(): void;
componentWillUnmount(): void;
render(): React.ReactElement<{
ref: React.RefObject<HTMLElement | null>;
id: string;
tabIndex: number | undefined;
}, string | React.JSXElementConstructor<any>>;
private changeEvents;
private beforeInputEvent;
private renderInline;
private renderIframe;
private getScriptSources;
private getInitialValue;
private bindHandlers;
private rollbackChange;
private handleBeforeInput;
private handleBeforeInputSpecial;
private handleEditorChange;
private handleEditorChangeSpecial;
private initialise;
}
export {};

605

lib/cjs/main/ts/components/Editor.js
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {

@@ -23,302 +38,175 @@ __assign = Object.assign || function(t) {

*/
var Editor = function (props) {
var _a, _b, _c;
var _d = props.cloudChannel, cloudChannel = _d === void 0 ? '7' : _d, tinymceScriptSrc = props.tinymceScriptSrc, onScriptsLoad = props.onScriptsLoad, onScriptsLoadError = props.onScriptsLoadError, initialValue = props.initialValue, value = props.value, rollback = props.rollback, onEditorChange = props.onEditorChange;
var editorRef = React.useRef();
var elementRef = React.useRef(null);
var id = React.useState(props.id || (0, Utils_1.uuid)('tiny-react'))[0];
var inline = React.useMemo(function () { var _a, _b, _c; return (_c = (_a = props.inline) !== null && _a !== void 0 ? _a : (_b = props.init) === null || _b === void 0 ? void 0 : _b.inline) !== null && _c !== void 0 ? _c : false; }, [props.inline, (_a = props.init) === null || _a === void 0 ? void 0 : _a.inline]);
var boundHandlersRef = React.useRef({});
var rollbackTimerRef = React.useRef();
var _e = React.useState(), valueCursor = _e[0], setValueCursor = _e[1];
var view = (_c = (_b = elementRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.defaultView) !== null && _c !== void 0 ? _c : window;
// Add other necessary useEffect hooks for handling prop changes
//
var changeEvents = React.useCallback(function () {
var Editor = /** @class */ (function (_super) {
__extends(Editor, _super);
function Editor(props) {
var _a, _b, _c;
var isIE = (_c = (_b = (_a = (0, TinyMCE_1.getTinymce)(view)) === null || _a === void 0 ? void 0 : _a.Env) === null || _b === void 0 ? void 0 : _b.browser) === null || _c === void 0 ? void 0 : _c.isIE();
return (isIE
? 'change keyup compositionend setcontent CommentChange'
: 'change input compositionend setcontent CommentChange');
}, [view]);
var beforeInputEvent = React.useCallback(function () { return (0, Utils_1.isBeforeInputEventAvailable)() ? 'beforeinput SelectionChange' : 'SelectionChange'; }, []);
var renderInline = function () {
var _a = props.tagName, tagName = _a === void 0 ? 'div' : _a;
return React.createElement(tagName, {
ref: elementRef,
id: id,
tabIndex: props.tabIndex
});
};
var renderIframe = function () { return React.createElement('textarea', {
ref: elementRef,
style: { visibility: 'hidden' },
name: props.textareaName,
id: id,
tabIndex: props.tabIndex
}); };
var getScriptSources = function () {
var _a, _b;
var async = (_a = props.scriptLoading) === null || _a === void 0 ? void 0 : _a.async;
var defer = (_b = props.scriptLoading) === null || _b === void 0 ? void 0 : _b.defer;
if (tinymceScriptSrc !== undefined) {
if (typeof tinymceScriptSrc === 'string') {
return [{ src: tinymceScriptSrc, async: async, defer: defer }];
var _this = _super.call(this, props) || this;
_this.rollbackTimer = undefined;
_this.valueCursor = undefined;
_this.rollbackChange = function () {
var editor = _this.editor;
var value = _this.props.value;
if (editor && value && value !== _this.currentContent) {
editor.undoManager.ignore(function () {
editor.setContent(value);
// only restore cursor on inline editors when they are focused
// as otherwise it will cause a focus grab
if (_this.valueCursor && (!_this.inline || editor.hasFocus())) {
try {
editor.selection.moveToBookmark(_this.valueCursor);
}
catch (e) { /* ignore */ }
}
});
}
// multiple scripts can be specified which allows for hybrid mode
return tinymceScriptSrc.map(function (item) {
if (typeof item === 'string') {
// async does not make sense for multiple items unless
// they are not dependent (which will be unlikely)
return { src: item, async: async, defer: defer };
}
else {
return item;
}
});
}
// fallback to the cloud when the tinymceScriptSrc is not specified
var channel = cloudChannel; // `cloudChannel` is in `defaultProps`, so it's always defined.
var apiKey = props.apiKey ? props.apiKey : 'no-api-key';
var cloudTinyJs = "https://cdn.tiny.cloud/1/".concat(apiKey, "/tinymce/").concat(channel, "/tinymce.min.js");
return [{ src: cloudTinyJs, async: async, defer: defer }];
};
var getInitialValue = function () {
if (typeof initialValue === 'string') {
return initialValue;
}
else if (typeof value === 'string') {
return value;
}
else {
return '';
}
};
var rollbackChange = React.useCallback(function () {
var editor = editorRef.current;
var content = editor === null || editor === void 0 ? void 0 : editor.getContent();
if (editor && value && value !== content) {
editor.undoManager.ignore(function () {
editor.setContent(value);
// only restore cursor on inline editors when they are focused
// as otherwise it will cause a focus grab
if (valueCursor && (!inline || editor.hasFocus())) {
_this.rollbackTimer = undefined;
};
_this.handleBeforeInput = function (_evt) {
if (_this.props.value !== undefined && _this.props.value === _this.currentContent && _this.editor) {
if (!_this.inline || _this.editor.hasFocus()) {
try {
editor.selection.moveToBookmark(valueCursor);
// getBookmark throws exceptions when the editor has not been focused
// possibly only in inline mode but I'm not taking chances
_this.valueCursor = _this.editor.selection.getBookmark(3);
}
catch (e) { /* ignore */ }
}
});
}
rollbackTimerRef.current = undefined;
}, [value, inline, valueCursor]);
var bindHandlers = React.useCallback(function () {
if (editorRef.current !== undefined) {
// typescript chokes trying to understand the type of the lookup function
(0, Utils_1.configHandlers)(editorRef.current, props, boundHandlersRef.current, function (key) { return props[key]; });
// check if we should monitor editor changes
}
}, [props]);
var handleBeforeInput = React.useCallback(function (_evt) {
var _a;
var content = (_a = editorRef.current) === null || _a === void 0 ? void 0 : _a.getContent();
if (value !== undefined && value === content && editorRef.current) {
if (!inline || editorRef.current.hasFocus()) {
try {
// getBookmark throws exceptions when the editor has not been focused
// possibly only in inline mode but I'm not taking chances
setValueCursor(editorRef.current.selection.getBookmark(3));
}
catch (e) { /* ignore */ }
}
}
}, [value, inline]);
var handleBeforeInputSpecial = React.useCallback(function (evt) {
if (evt.key === 'Enter' || evt.key === 'Backspace' || evt.key === 'Delete') {
handleBeforeInput(evt);
}
}, [handleBeforeInput]);
var handleEditorChange = React.useCallback(function (_evt) {
var currentEditor = editorRef.current;
if (currentEditor && currentEditor.initialized) {
var newContent = currentEditor.getContent();
if (value !== undefined && value !== newContent && rollback !== false) {
// start a timer and revert to the value if not applied in time
if (!rollbackTimerRef.current) {
rollbackTimerRef.current = window.setTimeout(rollbackChange, typeof rollback === 'number' ? rollback : 200);
};
_this.handleBeforeInputSpecial = function (evt) {
if (evt.key === 'Enter' || evt.key === 'Backspace' || evt.key === 'Delete') {
_this.handleBeforeInput(evt);
}
};
_this.handleEditorChange = function (_evt) {
var editor = _this.editor;
if (editor && editor.initialized) {
var newContent = editor.getContent();
if (_this.props.value !== undefined && _this.props.value !== newContent && _this.props.rollback !== false) {
// start a timer and revert to the value if not applied in time
if (!_this.rollbackTimer) {
_this.rollbackTimer = window.setTimeout(_this.rollbackChange, typeof _this.props.rollback === 'number' ? _this.props.rollback : 200);
}
}
if (newContent !== _this.currentContent) {
_this.currentContent = newContent;
if ((0, Utils_1.isFunction)(_this.props.onEditorChange)) {
_this.props.onEditorChange(newContent, editor);
}
}
}
if ((0, Utils_1.isFunction)(onEditorChange)) {
onEditorChange(newContent, currentEditor);
};
_this.handleEditorChangeSpecial = function (evt) {
if (evt.key === 'Backspace' || evt.key === 'Delete') {
_this.handleEditorChange(evt);
}
}
}, [value, onEditorChange, rollback, rollbackChange]);
var handleEditorChangeSpecial = React.useCallback(function (evt) {
if (evt.key === 'Backspace' || evt.key === 'Delete') {
handleEditorChange(evt);
}
}, [handleEditorChange]);
React.useEffect(function () {
if (editorRef.current !== undefined) {
// check if we should monitor editor changes
var isValueControlled = onEditorChange !== undefined || value !== undefined;
if (isValueControlled) {
editorRef.current.on(changeEvents(), handleEditorChange);
editorRef.current.on(beforeInputEvent(), handleBeforeInput);
editorRef.current.on('keydown', handleBeforeInputSpecial);
editorRef.current.on('keyup', handleEditorChangeSpecial);
editorRef.current.on('NewBlock', handleEditorChange);
};
_this.initialise = function (attempts) {
var _a, _b, _c;
if (attempts === void 0) { attempts = 0; }
var target = _this.elementRef.current;
if (!target) {
return; // Editor has been unmounted
}
else {
editorRef.current.off(changeEvents(), handleEditorChange);
editorRef.current.off(beforeInputEvent(), handleBeforeInput);
editorRef.current.off('keydown', handleBeforeInputSpecial);
editorRef.current.off('keyup', handleEditorChangeSpecial);
editorRef.current.off('NewBlock', handleEditorChange);
if (!(0, Utils_1.isInDoc)(target)) {
// this is probably someone trying to help by rendering us offscreen
// but we can't do that because the editor iframe must be in the document
// in order to have state
if (attempts === 0) {
// we probably just need to wait for the current events to be processed
setTimeout(function () { return _this.initialise(1); }, 1);
}
else if (attempts < 100) {
// wait for ten seconds, polling every tenth of a second
setTimeout(function () { return _this.initialise(attempts + 1); }, 100);
}
else {
// give up, at this point it seems that more polling is unlikely to help
throw new Error('tinymce can only be initialised when in a document');
}
return;
}
}
}, [
onEditorChange,
value,
handleEditorChange,
handleBeforeInput,
handleBeforeInputSpecial,
handleEditorChangeSpecial,
beforeInputEvent,
changeEvents
]);
var initialise = React.useCallback(function (attempts) {
var _a, _b, _c;
if (attempts === void 0) { attempts = 0; }
var target = elementRef.current;
if (!target) {
return; // Editor has been unmounted
}
if (!(0, Utils_1.isInDoc)(target)) {
// is probably someone trying to help by rendering us offscreen
// but we can't do that because the editor iframe must be in the document
// in order to have state
if (attempts === 0) {
// we probably just need to wait for the current events to be processed
setTimeout(function () { return initialise(1); }, 1);
var tinymce = (0, TinyMCE_1.getTinymce)(_this.view);
if (!tinymce) {
throw new Error('tinymce should have been loaded into global scope');
}
else if (attempts < 100) {
// wait for ten seconds, polling every tenth of a second
setTimeout(function () { return initialise(attempts + 1); }, 100);
var finalInit = __assign(__assign(__assign(__assign({}, _this.props.init), { selector: undefined, target: target, readonly: _this.props.disabled, inline: _this.inline, plugins: (0, Utils_1.mergePlugins)((_a = _this.props.init) === null || _a === void 0 ? void 0 : _a.plugins, _this.props.plugins), toolbar: (_b = _this.props.toolbar) !== null && _b !== void 0 ? _b : (_c = _this.props.init) === null || _c === void 0 ? void 0 : _c.toolbar }), (_this.props.licenseKey ? { license_key: _this.props.licenseKey } : {})), { setup: function (editor) {
_this.editor = editor;
_this.bindHandlers({});
// When running in inline mode the editor gets the initial value
// from the innerHTML of the element it is initialized on.
// However we don't want to take on the responsibility of sanitizing
// to remove XSS in the react integration so we have a chicken and egg
// problem... We avoid it by sneaking in a set content before the first
// "official" setContent and using TinyMCE to do the sanitization.
if (_this.inline && !(0, Utils_1.isTextareaOrInput)(target)) {
editor.once('PostRender', function (_evt) {
editor.setContent(_this.getInitialValue(), { no_events: true });
});
}
if (_this.props.init && (0, Utils_1.isFunction)(_this.props.init.setup)) {
_this.props.init.setup(editor);
}
}, init_instance_callback: function (editor) {
var _a, _b;
// check for changes that happened since tinymce.init() was called
var initialValue = _this.getInitialValue();
_this.currentContent = (_a = _this.currentContent) !== null && _a !== void 0 ? _a : editor.getContent();
if (_this.currentContent !== initialValue) {
_this.currentContent = initialValue;
// same as resetContent in TinyMCE 5
editor.setContent(initialValue);
editor.undoManager.clear();
editor.undoManager.add();
editor.setDirty(false);
}
var disabled = (_b = _this.props.disabled) !== null && _b !== void 0 ? _b : false;
(0, Utils_1.setMode)(_this.editor, disabled ? 'readonly' : 'design');
// ensure existing init_instance_callback is called
if (_this.props.init && (0, Utils_1.isFunction)(_this.props.init.init_instance_callback)) {
_this.props.init.init_instance_callback(editor);
}
} });
if (!_this.inline) {
target.style.visibility = '';
}
else {
// give up, at point it seems that more polling is unlikely to help
throw new Error('tinymce can only be initialised when in a document');
if ((0, Utils_1.isTextareaOrInput)(target)) {
target.value = _this.getInitialValue();
}
return;
}
var tinymce = (0, TinyMCE_1.getTinymce)(view);
if (!tinymce) {
throw new Error('tinymce should have been loaded into global scope');
}
var finalInit = __assign(__assign(__assign(__assign({}, props.init), { selector: undefined, target: target, readonly: props.disabled, inline: inline, plugins: (0, Utils_1.mergePlugins)((_a = props.init) === null || _a === void 0 ? void 0 : _a.plugins, props.plugins), toolbar: (_b = props.toolbar) !== null && _b !== void 0 ? _b : (_c = props.init) === null || _c === void 0 ? void 0 : _c.toolbar }), (props.licenseKey ? { license_key: props.licenseKey } : {})), { setup: function (ed) {
editorRef.current = ed;
bindHandlers();
// When running in inline mode the editor gets the initial value
// from the innerHTML of the element it is initialized on.
// However we don't want to take on the responsibility of sanitizing
// to remove XSS in the react integration so we have a chicken and egg
// problem... We avoid it by sneaking in a set content before the first
// "official" setContent and using TinyMCE to do the sanitization.
if (inline && !(0, Utils_1.isTextareaOrInput)(target)) {
ed.once('PostRender', function (_evt) {
ed.setContent(getInitialValue(), { no_events: true });
});
}
if (props.init && (0, Utils_1.isFunction)(props.init.setup)) {
props.init.setup(ed);
}
}, init_instance_callback: function (editor) {
var _a;
// check for changes that happened since tinymce.init() was called
var retrievedInitialValue = getInitialValue();
var currentEditorContent = editor.getContent();
if (currentEditorContent !== retrievedInitialValue) {
// same as resetContent in TinyMCE 5
editor.setContent(retrievedInitialValue);
editor.undoManager.clear();
editor.undoManager.add();
editor.setDirty(false);
}
var disabled = (_a = props.disabled) !== null && _a !== void 0 ? _a : false;
(0, Utils_1.setMode)(editor, disabled ? 'readonly' : 'design');
// ensure existing init_instance_callback is called
if (props.init && (0, Utils_1.isFunction)(props.init.init_instance_callback)) {
props.init.init_instance_callback(editor);
}
} });
if (!inline) {
target.style.visibility = '';
}
if ((0, Utils_1.isTextareaOrInput)(target)) {
target.value = getInitialValue();
}
tinymce.init(finalInit);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
React.useEffect(function () {
var _a, _b, _c;
if ((0, TinyMCE_1.getTinymce)(view) !== null) {
initialise();
}
else if (Array.isArray(tinymceScriptSrc) && tinymceScriptSrc.length === 0) {
onScriptsLoadError === null || onScriptsLoadError === void 0 ? void 0 : onScriptsLoadError(new Error('No `tinymce` global is present but the `tinymceScriptSrc` prop was an empty array.'));
}
else if ((_a = elementRef.current) === null || _a === void 0 ? void 0 : _a.ownerDocument) {
var successHandler = function () {
onScriptsLoad === null || onScriptsLoad === void 0 ? void 0 : onScriptsLoad();
initialise();
};
var errorHandler = function (err) {
onScriptsLoadError === null || onScriptsLoadError === void 0 ? void 0 : onScriptsLoadError(err);
};
ScriptLoader2_1.ScriptLoader.loadList(elementRef.current.ownerDocument, getScriptSources(), (_c = (_b = props.scriptLoading) === null || _b === void 0 ? void 0 : _b.delay) !== null && _c !== void 0 ? _c : 0, successHandler, errorHandler);
}
var boundHandlers = boundHandlersRef.current;
return function () {
var editor = editorRef.current;
if (editor) {
editor.off(changeEvents(), handleEditorChange);
editor.off(beforeInputEvent(), handleBeforeInput);
editor.off('keypress', handleEditorChangeSpecial);
editor.off('keydown', handleBeforeInputSpecial);
editor.off('NewBlock', handleEditorChange);
Object.keys(boundHandlers).forEach(function (eventName) {
editor.off(eventName, boundHandlers[eventName]);
});
boundHandlersRef.current = {};
editor.remove();
editorRef.current = undefined;
}
tinymce.init(finalInit);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
React.useEffect(function () {
if (editorRef.current) {
bindHandlers();
}
_this.id = _this.props.id || (0, Utils_1.uuid)('tiny-react');
_this.elementRef = React.createRef();
_this.inline = (_c = (_a = _this.props.inline) !== null && _a !== void 0 ? _a : (_b = _this.props.init) === null || _b === void 0 ? void 0 : _b.inline) !== null && _c !== void 0 ? _c : false;
_this.boundHandlers = {};
return _this;
}
Object.defineProperty(Editor.prototype, "view", {
get: function () {
var _a, _b;
return (_b = (_a = this.elementRef.current) === null || _a === void 0 ? void 0 : _a.ownerDocument.defaultView) !== null && _b !== void 0 ? _b : window;
},
enumerable: false,
configurable: true
});
React.useEffect(function () {
var _a;
if (rollbackTimerRef.current) {
clearTimeout(rollbackTimerRef.current);
rollbackTimerRef.current = undefined;
Editor.prototype.componentDidUpdate = function (prevProps) {
var _this = this;
var _a, _b;
if (this.rollbackTimer) {
clearTimeout(this.rollbackTimer);
this.rollbackTimer = undefined;
}
if (editorRef.current) {
if (editorRef.current.initialized) {
var editorContent = editorRef.current.getContent();
if (typeof initialValue === 'string') {
if (this.editor) {
this.bindHandlers(prevProps);
if (this.editor.initialized) {
this.currentContent = (_a = this.currentContent) !== null && _a !== void 0 ? _a : this.editor.getContent();
if (typeof this.props.initialValue === 'string' && this.props.initialValue !== prevProps.initialValue) {
// same as resetContent in TinyMCE 5
editorRef.current.setContent(initialValue);
editorRef.current.undoManager.clear();
editorRef.current.undoManager.add();
editorRef.current.setDirty(false);
this.editor.setContent(this.props.initialValue);
this.editor.undoManager.clear();
this.editor.undoManager.add();
this.editor.setDirty(false);
}
else if (typeof value === 'string' && value !== editorContent) {
var localEditor_1 = editorRef.current;
else if (typeof this.props.value === 'string' && this.props.value !== this.currentContent) {
var localEditor_1 = this.editor;
localEditor_1.undoManager.transact(function () {

@@ -328,3 +216,3 @@ // inline editors grab focus when restoring selection

var cursor;
if (!inline || localEditor_1.hasFocus()) {
if (!_this.inline || localEditor_1.hasFocus()) {
try {

@@ -337,4 +225,5 @@ // getBookmark throws exceptions when the editor has not been focused

}
localEditor_1.setContent(value);
if (!inline || localEditor_1.hasFocus()) {
var valueCursor = _this.valueCursor;
localEditor_1.setContent(_this.props.value);
if (!_this.inline || localEditor_1.hasFocus()) {
for (var _i = 0, _a = [cursor, valueCursor]; _i < _a.length; _i++) {

@@ -345,3 +234,3 @@ var bookmark = _a[_i];

localEditor_1.selection.moveToBookmark(bookmark);
setValueCursor(bookmark);
_this.valueCursor = bookmark;
break;

@@ -355,12 +244,146 @@ }

}
if (props.disabled) {
var disabled = (_a = props.disabled) !== null && _a !== void 0 ? _a : false;
(0, Utils_1.setMode)(editorRef.current, disabled ? 'readonly' : 'design');
if (this.props.disabled !== prevProps.disabled) {
var disabled = (_b = this.props.disabled) !== null && _b !== void 0 ? _b : false;
(0, Utils_1.setMode)(this.editor, disabled ? 'readonly' : 'design');
}
}
}
}, [initialValue, inline, props.disabled, value, valueCursor]);
return inline ? renderInline() : renderIframe();
};
};
Editor.prototype.componentDidMount = function () {
var _this = this;
var _a, _b, _c, _d, _e;
if ((0, TinyMCE_1.getTinymce)(this.view) !== null) {
this.initialise();
}
else if (Array.isArray(this.props.tinymceScriptSrc) && this.props.tinymceScriptSrc.length === 0) {
(_b = (_a = this.props).onScriptsLoadError) === null || _b === void 0 ? void 0 : _b.call(_a, new Error('No `tinymce` global is present but the `tinymceScriptSrc` prop was an empty array.'));
}
else if ((_c = this.elementRef.current) === null || _c === void 0 ? void 0 : _c.ownerDocument) {
var successHandler = function () {
var _a, _b;
(_b = (_a = _this.props).onScriptsLoad) === null || _b === void 0 ? void 0 : _b.call(_a);
_this.initialise();
};
var errorHandler = function (err) {
var _a, _b;
(_b = (_a = _this.props).onScriptsLoadError) === null || _b === void 0 ? void 0 : _b.call(_a, err);
};
ScriptLoader2_1.ScriptLoader.loadList(this.elementRef.current.ownerDocument, this.getScriptSources(), (_e = (_d = this.props.scriptLoading) === null || _d === void 0 ? void 0 : _d.delay) !== null && _e !== void 0 ? _e : 0, successHandler, errorHandler);
}
};
Editor.prototype.componentWillUnmount = function () {
var _this = this;
var editor = this.editor;
if (editor) {
editor.off(this.changeEvents(), this.handleEditorChange);
editor.off(this.beforeInputEvent(), this.handleBeforeInput);
editor.off('keypress', this.handleEditorChangeSpecial);
editor.off('keydown', this.handleBeforeInputSpecial);
editor.off('NewBlock', this.handleEditorChange);
Object.keys(this.boundHandlers).forEach(function (eventName) {
editor.off(eventName, _this.boundHandlers[eventName]);
});
this.boundHandlers = {};
editor.remove();
this.editor = undefined;
}
};
Editor.prototype.render = function () {
return this.inline ? this.renderInline() : this.renderIframe();
};
Editor.prototype.changeEvents = function () {
var _a, _b, _c;
var isIE = (_c = (_b = (_a = (0, TinyMCE_1.getTinymce)(this.view)) === null || _a === void 0 ? void 0 : _a.Env) === null || _b === void 0 ? void 0 : _b.browser) === null || _c === void 0 ? void 0 : _c.isIE();
return (isIE
? 'change keyup compositionend setcontent CommentChange'
: 'change input compositionend setcontent CommentChange');
};
Editor.prototype.beforeInputEvent = function () {
return (0, Utils_1.isBeforeInputEventAvailable)() ? 'beforeinput SelectionChange' : 'SelectionChange';
};
Editor.prototype.renderInline = function () {
var _a = this.props.tagName, tagName = _a === void 0 ? 'div' : _a;
return React.createElement(tagName, {
ref: this.elementRef,
id: this.id,
tabIndex: this.props.tabIndex
});
};
Editor.prototype.renderIframe = function () {
return React.createElement('textarea', {
ref: this.elementRef,
style: { visibility: 'hidden' },
name: this.props.textareaName,
id: this.id,
tabIndex: this.props.tabIndex
});
};
Editor.prototype.getScriptSources = function () {
var _a, _b;
var async = (_a = this.props.scriptLoading) === null || _a === void 0 ? void 0 : _a.async;
var defer = (_b = this.props.scriptLoading) === null || _b === void 0 ? void 0 : _b.defer;
if (this.props.tinymceScriptSrc !== undefined) {
if (typeof this.props.tinymceScriptSrc === 'string') {
return [{ src: this.props.tinymceScriptSrc, async: async, defer: defer }];
}
// multiple scripts can be specified which allows for hybrid mode
return this.props.tinymceScriptSrc.map(function (item) {
if (typeof item === 'string') {
// async does not make sense for multiple items unless
// they are not dependent (which will be unlikely)
return { src: item, async: async, defer: defer };
}
else {
return item;
}
});
}
// fallback to the cloud when the tinymceScriptSrc is not specified
var channel = this.props.cloudChannel; // `cloudChannel` is in `defaultProps`, so it's always defined.
var apiKey = this.props.apiKey ? this.props.apiKey : 'no-api-key';
var cloudTinyJs = "https://cdn.tiny.cloud/1/".concat(apiKey, "/tinymce/").concat(channel, "/tinymce.min.js");
return [{ src: cloudTinyJs, async: async, defer: defer }];
};
Editor.prototype.getInitialValue = function () {
if (typeof this.props.initialValue === 'string') {
return this.props.initialValue;
}
else if (typeof this.props.value === 'string') {
return this.props.value;
}
else {
return '';
}
};
Editor.prototype.bindHandlers = function (prevProps) {
var _this = this;
if (this.editor !== undefined) {
// typescript chokes trying to understand the type of the lookup function
(0, Utils_1.configHandlers)(this.editor, prevProps, this.props, this.boundHandlers, function (key) { return _this.props[key]; });
// check if we should monitor editor changes
var isValueControlled = function (p) { return p.onEditorChange !== undefined || p.value !== undefined; };
var wasControlled = isValueControlled(prevProps);
var nowControlled = isValueControlled(this.props);
if (!wasControlled && nowControlled) {
this.editor.on(this.changeEvents(), this.handleEditorChange);
this.editor.on(this.beforeInputEvent(), this.handleBeforeInput);
this.editor.on('keydown', this.handleBeforeInputSpecial);
this.editor.on('keyup', this.handleEditorChangeSpecial);
this.editor.on('NewBlock', this.handleEditorChange);
}
else if (wasControlled && !nowControlled) {
this.editor.off(this.changeEvents(), this.handleEditorChange);
this.editor.off(this.beforeInputEvent(), this.handleBeforeInput);
this.editor.off('keydown', this.handleBeforeInputSpecial);
this.editor.off('keyup', this.handleEditorChangeSpecial);
this.editor.off('NewBlock', this.handleEditorChange);
}
}
};
Editor.propTypes = EditorPropTypes_1.EditorPropTypes;
Editor.defaultProps = {
cloudChannel: '7',
};
return Editor;
}(React.Component));
exports.Editor = Editor;
exports.Editor.propTypes = EditorPropTypes_1.EditorPropTypes;

@@ -5,3 +5,3 @@ import * as PropTypes from 'prop-types';

export type CopyProps<T> = {
[P in keyof T]: PropTypes.Requireable<any>;
[P in keyof T]: PropTypes.Requireable<unknown>;
};

@@ -8,0 +8,0 @@ export type IEventPropTypes = CopyProps<IEvents>;

@@ -1,8 +0,8 @@

import type { EditorEvent, Editor as TinyMCEEditor } from 'tinymce';
import { IEventPropTypes } from './components/EditorPropTypes';
import { IAllProps } from './components/Editor';
import { IEventPropTypes } from './components/EditorPropTypes';
import type { Editor as TinyMCEEditor, EditorEvent } from 'tinymce';
export declare const isFunction: (x: unknown) => x is Function;
type PropLookup = <K extends keyof IAllProps>(key: K) => IAllProps[K] | undefined;
export declare const configHandlers2: <H>(handlerLookup: PropLookup, on: (name: string, handler: H) => void, off: (name: string, handler: H) => void, adapter: <K extends keyof IEventPropTypes>(lookup: PropLookup, key: K) => H, props: Partial<IAllProps>, boundHandlers: Record<string, H>) => void;
export declare const configHandlers: (editor: TinyMCEEditor, props: Partial<IAllProps>, boundHandlers: Record<string, (event: EditorEvent<any>) => unknown>, lookup: PropLookup) => void;
export declare const configHandlers2: <H>(handlerLookup: PropLookup, on: (name: string, handler: H) => void, off: (name: string, handler: H) => void, adapter: <K extends keyof IEventPropTypes>(lookup: PropLookup, key: K) => H, prevProps: Partial<IAllProps>, props: Partial<IAllProps>, boundHandlers: Record<string, H>) => void;
export declare const configHandlers: (editor: TinyMCEEditor, prevProps: Partial<IAllProps>, props: Partial<IAllProps>, boundHandlers: Record<string, (event: EditorEvent<any>) => unknown>, lookup: PropLookup) => void;
export declare const uuid: (prefix: string) => string;

@@ -9,0 +9,0 @@ export declare const isTextareaOrInput: (element: Element | null) => element is (HTMLTextAreaElement | HTMLInputElement);

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setMode = exports.isInDoc = exports.isBeforeInputEventAvailable = exports.mergePlugins = exports.isTextareaOrInput = exports.uuid = exports.configHandlers = exports.configHandlers2 = exports.isFunction = void 0;
var katamari_1 = require("@ephox/katamari");
var EditorPropTypes_1 = require("./components/EditorPropTypes");

@@ -10,16 +9,15 @@ var isFunction = function (x) { return typeof x === 'function'; };

var eventAttrToEventName = function (attrName) { return attrName.substr(2); };
var configHandlers2 = function (handlerLookup, on, off, adapter, props, boundHandlers) {
var eventKeys = Object.keys(EditorPropTypes_1.eventPropTypes);
var configHandlers2 = function (handlerLookup, on, off, adapter, prevProps, props, boundHandlers) {
var prevEventKeys = Object.keys(prevProps).filter(isEventProp);
var currEventKeys = Object.keys(props).filter(isEventProp);
var unboundEventKeys = eventKeys.filter(function (key) { return props[key] === undefined; });
unboundEventKeys.forEach(function (key) {
var removedKeys = prevEventKeys.filter(function (key) { return props[key] === undefined; });
var addedKeys = currEventKeys.filter(function (key) { return prevProps[key] === undefined; });
removedKeys.forEach(function (key) {
// remove event handler
var eventName = eventAttrToEventName(key);
var wrappedHandler = boundHandlers[eventName];
if (katamari_1.Type.isNonNullable(wrappedHandler)) {
off(eventName, wrappedHandler);
delete boundHandlers[eventName];
}
off(eventName, wrappedHandler);
delete boundHandlers[eventName];
});
currEventKeys.forEach(function (key) {
addedKeys.forEach(function (key) {
var wrappedHandler = adapter(handlerLookup, key);

@@ -32,6 +30,6 @@ var eventName = eventAttrToEventName(key);

exports.configHandlers2 = configHandlers2;
var configHandlers = function (editor, props, boundHandlers, lookup) {
var configHandlers = function (editor, prevProps, props, boundHandlers, lookup) {
return (0, exports.configHandlers2)(lookup, editor.on.bind(editor), editor.off.bind(editor),
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
function (handlerLookup, key) { return function (e) { var _a; return (_a = handlerLookup(key)) === null || _a === void 0 ? void 0 : _a(e, editor); }; }, props, boundHandlers);
function (handlerLookup, key) { return function (e) { var _a; return (_a = handlerLookup(key)) === null || _a === void 0 ? void 0 : _a(e, editor); }; }, prevProps, props, boundHandlers);
};

@@ -38,0 +36,0 @@ exports.configHandlers = configHandlers;

import * as React from 'react';
import type { TinyMCE, Editor as TinyMCEEditor } from 'tinymce';
import { IEvents } from '../Events';
import { ScriptItem } from '../ScriptLoader2';
import { IEditorPropTypes } from './EditorPropTypes';
import type { Editor as TinyMCEEditor, TinyMCE } from 'tinymce';
type OmitStringIndexSignature<T> = {

@@ -131,3 +132,37 @@ [K in keyof T as string extends K ? never : K]: T[K];

*/
export declare const Editor: React.FC<IAllProps>;
export declare class Editor extends React.Component<IAllProps> {
static propTypes: IEditorPropTypes;
static defaultProps: Partial<IAllProps>;
editor?: TinyMCEEditor;
private id;
private elementRef;
private inline;
private currentContent?;
private boundHandlers;
private rollbackTimer;
private valueCursor;
constructor(props: Partial<IAllProps>);
private get view();
componentDidUpdate(prevProps: Partial<IAllProps>): void;
componentDidMount(): void;
componentWillUnmount(): void;
render(): React.ReactElement<{
ref: React.RefObject<HTMLElement | null>;
id: string;
tabIndex: number | undefined;
}, string | React.JSXElementConstructor<any>>;
private changeEvents;
private beforeInputEvent;
private renderInline;
private renderIframe;
private getScriptSources;
private getInitialValue;
private bindHandlers;
private rollbackChange;
private handleBeforeInput;
private handleBeforeInputSpecial;
private handleEditorChange;
private handleEditorChangeSpecial;
private initialise;
}
export {};

@@ -0,1 +1,16 @@

var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __assign = (this && this.__assign) || function () {

@@ -15,3 +30,3 @@ __assign = Object.assign || function(t) {

import { getTinymce } from '../TinyMCE';
import { configHandlers, isBeforeInputEventAvailable, isFunction, isInDoc, isTextareaOrInput, mergePlugins, setMode, uuid } from '../Utils';
import { isFunction, isTextareaOrInput, mergePlugins, uuid, configHandlers, isBeforeInputEventAvailable, isInDoc, setMode } from '../Utils';
import { EditorPropTypes } from './EditorPropTypes';

@@ -21,302 +36,175 @@ /**

*/
export var Editor = function (props) {
var _a, _b, _c;
var _d = props.cloudChannel, cloudChannel = _d === void 0 ? '7' : _d, tinymceScriptSrc = props.tinymceScriptSrc, onScriptsLoad = props.onScriptsLoad, onScriptsLoadError = props.onScriptsLoadError, initialValue = props.initialValue, value = props.value, rollback = props.rollback, onEditorChange = props.onEditorChange;
var editorRef = React.useRef();
var elementRef = React.useRef(null);
var id = React.useState(props.id || uuid('tiny-react'))[0];
var inline = React.useMemo(function () { var _a, _b, _c; return (_c = (_a = props.inline) !== null && _a !== void 0 ? _a : (_b = props.init) === null || _b === void 0 ? void 0 : _b.inline) !== null && _c !== void 0 ? _c : false; }, [props.inline, (_a = props.init) === null || _a === void 0 ? void 0 : _a.inline]);
var boundHandlersRef = React.useRef({});
var rollbackTimerRef = React.useRef();
var _e = React.useState(), valueCursor = _e[0], setValueCursor = _e[1];
var view = (_c = (_b = elementRef.current) === null || _b === void 0 ? void 0 : _b.ownerDocument.defaultView) !== null && _c !== void 0 ? _c : window;
// Add other necessary useEffect hooks for handling prop changes
//
var changeEvents = React.useCallback(function () {
var Editor = /** @class */ (function (_super) {
__extends(Editor, _super);
function Editor(props) {
var _a, _b, _c;
var isIE = (_c = (_b = (_a = getTinymce(view)) === null || _a === void 0 ? void 0 : _a.Env) === null || _b === void 0 ? void 0 : _b.browser) === null || _c === void 0 ? void 0 : _c.isIE();
return (isIE
? 'change keyup compositionend setcontent CommentChange'
: 'change input compositionend setcontent CommentChange');
}, [view]);
var beforeInputEvent = React.useCallback(function () { return isBeforeInputEventAvailable() ? 'beforeinput SelectionChange' : 'SelectionChange'; }, []);
var renderInline = function () {
var _a = props.tagName, tagName = _a === void 0 ? 'div' : _a;
return React.createElement(tagName, {
ref: elementRef,
id: id,
tabIndex: props.tabIndex
});
};
var renderIframe = function () { return React.createElement('textarea', {
ref: elementRef,
style: { visibility: 'hidden' },
name: props.textareaName,
id: id,
tabIndex: props.tabIndex
}); };
var getScriptSources = function () {
var _a, _b;
var async = (_a = props.scriptLoading) === null || _a === void 0 ? void 0 : _a.async;
var defer = (_b = props.scriptLoading) === null || _b === void 0 ? void 0 : _b.defer;
if (tinymceScriptSrc !== undefined) {
if (typeof tinymceScriptSrc === 'string') {
return [{ src: tinymceScriptSrc, async: async, defer: defer }];
var _this = _super.call(this, props) || this;
_this.rollbackTimer = undefined;
_this.valueCursor = undefined;
_this.rollbackChange = function () {
var editor = _this.editor;
var value = _this.props.value;
if (editor && value && value !== _this.currentContent) {
editor.undoManager.ignore(function () {
editor.setContent(value);
// only restore cursor on inline editors when they are focused
// as otherwise it will cause a focus grab
if (_this.valueCursor && (!_this.inline || editor.hasFocus())) {
try {
editor.selection.moveToBookmark(_this.valueCursor);
}
catch (e) { /* ignore */ }
}
});
}
// multiple scripts can be specified which allows for hybrid mode
return tinymceScriptSrc.map(function (item) {
if (typeof item === 'string') {
// async does not make sense for multiple items unless
// they are not dependent (which will be unlikely)
return { src: item, async: async, defer: defer };
}
else {
return item;
}
});
}
// fallback to the cloud when the tinymceScriptSrc is not specified
var channel = cloudChannel; // `cloudChannel` is in `defaultProps`, so it's always defined.
var apiKey = props.apiKey ? props.apiKey : 'no-api-key';
var cloudTinyJs = "https://cdn.tiny.cloud/1/".concat(apiKey, "/tinymce/").concat(channel, "/tinymce.min.js");
return [{ src: cloudTinyJs, async: async, defer: defer }];
};
var getInitialValue = function () {
if (typeof initialValue === 'string') {
return initialValue;
}
else if (typeof value === 'string') {
return value;
}
else {
return '';
}
};
var rollbackChange = React.useCallback(function () {
var editor = editorRef.current;
var content = editor === null || editor === void 0 ? void 0 : editor.getContent();
if (editor && value && value !== content) {
editor.undoManager.ignore(function () {
editor.setContent(value);
// only restore cursor on inline editors when they are focused
// as otherwise it will cause a focus grab
if (valueCursor && (!inline || editor.hasFocus())) {
_this.rollbackTimer = undefined;
};
_this.handleBeforeInput = function (_evt) {
if (_this.props.value !== undefined && _this.props.value === _this.currentContent && _this.editor) {
if (!_this.inline || _this.editor.hasFocus()) {
try {
editor.selection.moveToBookmark(valueCursor);
// getBookmark throws exceptions when the editor has not been focused
// possibly only in inline mode but I'm not taking chances
_this.valueCursor = _this.editor.selection.getBookmark(3);
}
catch (e) { /* ignore */ }
}
});
}
rollbackTimerRef.current = undefined;
}, [value, inline, valueCursor]);
var bindHandlers = React.useCallback(function () {
if (editorRef.current !== undefined) {
// typescript chokes trying to understand the type of the lookup function
configHandlers(editorRef.current, props, boundHandlersRef.current, function (key) { return props[key]; });
// check if we should monitor editor changes
}
}, [props]);
var handleBeforeInput = React.useCallback(function (_evt) {
var _a;
var content = (_a = editorRef.current) === null || _a === void 0 ? void 0 : _a.getContent();
if (value !== undefined && value === content && editorRef.current) {
if (!inline || editorRef.current.hasFocus()) {
try {
// getBookmark throws exceptions when the editor has not been focused
// possibly only in inline mode but I'm not taking chances
setValueCursor(editorRef.current.selection.getBookmark(3));
}
catch (e) { /* ignore */ }
}
}
}, [value, inline]);
var handleBeforeInputSpecial = React.useCallback(function (evt) {
if (evt.key === 'Enter' || evt.key === 'Backspace' || evt.key === 'Delete') {
handleBeforeInput(evt);
}
}, [handleBeforeInput]);
var handleEditorChange = React.useCallback(function (_evt) {
var currentEditor = editorRef.current;
if (currentEditor && currentEditor.initialized) {
var newContent = currentEditor.getContent();
if (value !== undefined && value !== newContent && rollback !== false) {
// start a timer and revert to the value if not applied in time
if (!rollbackTimerRef.current) {
rollbackTimerRef.current = window.setTimeout(rollbackChange, typeof rollback === 'number' ? rollback : 200);
};
_this.handleBeforeInputSpecial = function (evt) {
if (evt.key === 'Enter' || evt.key === 'Backspace' || evt.key === 'Delete') {
_this.handleBeforeInput(evt);
}
};
_this.handleEditorChange = function (_evt) {
var editor = _this.editor;
if (editor && editor.initialized) {
var newContent = editor.getContent();
if (_this.props.value !== undefined && _this.props.value !== newContent && _this.props.rollback !== false) {
// start a timer and revert to the value if not applied in time
if (!_this.rollbackTimer) {
_this.rollbackTimer = window.setTimeout(_this.rollbackChange, typeof _this.props.rollback === 'number' ? _this.props.rollback : 200);
}
}
if (newContent !== _this.currentContent) {
_this.currentContent = newContent;
if (isFunction(_this.props.onEditorChange)) {
_this.props.onEditorChange(newContent, editor);
}
}
}
if (isFunction(onEditorChange)) {
onEditorChange(newContent, currentEditor);
};
_this.handleEditorChangeSpecial = function (evt) {
if (evt.key === 'Backspace' || evt.key === 'Delete') {
_this.handleEditorChange(evt);
}
}
}, [value, onEditorChange, rollback, rollbackChange]);
var handleEditorChangeSpecial = React.useCallback(function (evt) {
if (evt.key === 'Backspace' || evt.key === 'Delete') {
handleEditorChange(evt);
}
}, [handleEditorChange]);
React.useEffect(function () {
if (editorRef.current !== undefined) {
// check if we should monitor editor changes
var isValueControlled = onEditorChange !== undefined || value !== undefined;
if (isValueControlled) {
editorRef.current.on(changeEvents(), handleEditorChange);
editorRef.current.on(beforeInputEvent(), handleBeforeInput);
editorRef.current.on('keydown', handleBeforeInputSpecial);
editorRef.current.on('keyup', handleEditorChangeSpecial);
editorRef.current.on('NewBlock', handleEditorChange);
};
_this.initialise = function (attempts) {
var _a, _b, _c;
if (attempts === void 0) { attempts = 0; }
var target = _this.elementRef.current;
if (!target) {
return; // Editor has been unmounted
}
else {
editorRef.current.off(changeEvents(), handleEditorChange);
editorRef.current.off(beforeInputEvent(), handleBeforeInput);
editorRef.current.off('keydown', handleBeforeInputSpecial);
editorRef.current.off('keyup', handleEditorChangeSpecial);
editorRef.current.off('NewBlock', handleEditorChange);
if (!isInDoc(target)) {
// this is probably someone trying to help by rendering us offscreen
// but we can't do that because the editor iframe must be in the document
// in order to have state
if (attempts === 0) {
// we probably just need to wait for the current events to be processed
setTimeout(function () { return _this.initialise(1); }, 1);
}
else if (attempts < 100) {
// wait for ten seconds, polling every tenth of a second
setTimeout(function () { return _this.initialise(attempts + 1); }, 100);
}
else {
// give up, at this point it seems that more polling is unlikely to help
throw new Error('tinymce can only be initialised when in a document');
}
return;
}
}
}, [
onEditorChange,
value,
handleEditorChange,
handleBeforeInput,
handleBeforeInputSpecial,
handleEditorChangeSpecial,
beforeInputEvent,
changeEvents
]);
var initialise = React.useCallback(function (attempts) {
var _a, _b, _c;
if (attempts === void 0) { attempts = 0; }
var target = elementRef.current;
if (!target) {
return; // Editor has been unmounted
}
if (!isInDoc(target)) {
// is probably someone trying to help by rendering us offscreen
// but we can't do that because the editor iframe must be in the document
// in order to have state
if (attempts === 0) {
// we probably just need to wait for the current events to be processed
setTimeout(function () { return initialise(1); }, 1);
var tinymce = getTinymce(_this.view);
if (!tinymce) {
throw new Error('tinymce should have been loaded into global scope');
}
else if (attempts < 100) {
// wait for ten seconds, polling every tenth of a second
setTimeout(function () { return initialise(attempts + 1); }, 100);
var finalInit = __assign(__assign(__assign(__assign({}, _this.props.init), { selector: undefined, target: target, readonly: _this.props.disabled, inline: _this.inline, plugins: mergePlugins((_a = _this.props.init) === null || _a === void 0 ? void 0 : _a.plugins, _this.props.plugins), toolbar: (_b = _this.props.toolbar) !== null && _b !== void 0 ? _b : (_c = _this.props.init) === null || _c === void 0 ? void 0 : _c.toolbar }), (_this.props.licenseKey ? { license_key: _this.props.licenseKey } : {})), { setup: function (editor) {
_this.editor = editor;
_this.bindHandlers({});
// When running in inline mode the editor gets the initial value
// from the innerHTML of the element it is initialized on.
// However we don't want to take on the responsibility of sanitizing
// to remove XSS in the react integration so we have a chicken and egg
// problem... We avoid it by sneaking in a set content before the first
// "official" setContent and using TinyMCE to do the sanitization.
if (_this.inline && !isTextareaOrInput(target)) {
editor.once('PostRender', function (_evt) {
editor.setContent(_this.getInitialValue(), { no_events: true });
});
}
if (_this.props.init && isFunction(_this.props.init.setup)) {
_this.props.init.setup(editor);
}
}, init_instance_callback: function (editor) {
var _a, _b;
// check for changes that happened since tinymce.init() was called
var initialValue = _this.getInitialValue();
_this.currentContent = (_a = _this.currentContent) !== null && _a !== void 0 ? _a : editor.getContent();
if (_this.currentContent !== initialValue) {
_this.currentContent = initialValue;
// same as resetContent in TinyMCE 5
editor.setContent(initialValue);
editor.undoManager.clear();
editor.undoManager.add();
editor.setDirty(false);
}
var disabled = (_b = _this.props.disabled) !== null && _b !== void 0 ? _b : false;
setMode(_this.editor, disabled ? 'readonly' : 'design');
// ensure existing init_instance_callback is called
if (_this.props.init && isFunction(_this.props.init.init_instance_callback)) {
_this.props.init.init_instance_callback(editor);
}
} });
if (!_this.inline) {
target.style.visibility = '';
}
else {
// give up, at point it seems that more polling is unlikely to help
throw new Error('tinymce can only be initialised when in a document');
if (isTextareaOrInput(target)) {
target.value = _this.getInitialValue();
}
return;
}
var tinymce = getTinymce(view);
if (!tinymce) {
throw new Error('tinymce should have been loaded into global scope');
}
var finalInit = __assign(__assign(__assign(__assign({}, props.init), { selector: undefined, target: target, readonly: props.disabled, inline: inline, plugins: mergePlugins((_a = props.init) === null || _a === void 0 ? void 0 : _a.plugins, props.plugins), toolbar: (_b = props.toolbar) !== null && _b !== void 0 ? _b : (_c = props.init) === null || _c === void 0 ? void 0 : _c.toolbar }), (props.licenseKey ? { license_key: props.licenseKey } : {})), { setup: function (ed) {
editorRef.current = ed;
bindHandlers();
// When running in inline mode the editor gets the initial value
// from the innerHTML of the element it is initialized on.
// However we don't want to take on the responsibility of sanitizing
// to remove XSS in the react integration so we have a chicken and egg
// problem... We avoid it by sneaking in a set content before the first
// "official" setContent and using TinyMCE to do the sanitization.
if (inline && !isTextareaOrInput(target)) {
ed.once('PostRender', function (_evt) {
ed.setContent(getInitialValue(), { no_events: true });
});
}
if (props.init && isFunction(props.init.setup)) {
props.init.setup(ed);
}
}, init_instance_callback: function (editor) {
var _a;
// check for changes that happened since tinymce.init() was called
var retrievedInitialValue = getInitialValue();
var currentEditorContent = editor.getContent();
if (currentEditorContent !== retrievedInitialValue) {
// same as resetContent in TinyMCE 5
editor.setContent(retrievedInitialValue);
editor.undoManager.clear();
editor.undoManager.add();
editor.setDirty(false);
}
var disabled = (_a = props.disabled) !== null && _a !== void 0 ? _a : false;
setMode(editor, disabled ? 'readonly' : 'design');
// ensure existing init_instance_callback is called
if (props.init && isFunction(props.init.init_instance_callback)) {
props.init.init_instance_callback(editor);
}
} });
if (!inline) {
target.style.visibility = '';
}
if (isTextareaOrInput(target)) {
target.value = getInitialValue();
}
tinymce.init(finalInit);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
React.useEffect(function () {
var _a, _b, _c;
if (getTinymce(view) !== null) {
initialise();
}
else if (Array.isArray(tinymceScriptSrc) && tinymceScriptSrc.length === 0) {
onScriptsLoadError === null || onScriptsLoadError === void 0 ? void 0 : onScriptsLoadError(new Error('No `tinymce` global is present but the `tinymceScriptSrc` prop was an empty array.'));
}
else if ((_a = elementRef.current) === null || _a === void 0 ? void 0 : _a.ownerDocument) {
var successHandler = function () {
onScriptsLoad === null || onScriptsLoad === void 0 ? void 0 : onScriptsLoad();
initialise();
};
var errorHandler = function (err) {
onScriptsLoadError === null || onScriptsLoadError === void 0 ? void 0 : onScriptsLoadError(err);
};
ScriptLoader.loadList(elementRef.current.ownerDocument, getScriptSources(), (_c = (_b = props.scriptLoading) === null || _b === void 0 ? void 0 : _b.delay) !== null && _c !== void 0 ? _c : 0, successHandler, errorHandler);
}
var boundHandlers = boundHandlersRef.current;
return function () {
var editor = editorRef.current;
if (editor) {
editor.off(changeEvents(), handleEditorChange);
editor.off(beforeInputEvent(), handleBeforeInput);
editor.off('keypress', handleEditorChangeSpecial);
editor.off('keydown', handleBeforeInputSpecial);
editor.off('NewBlock', handleEditorChange);
Object.keys(boundHandlers).forEach(function (eventName) {
editor.off(eventName, boundHandlers[eventName]);
});
boundHandlersRef.current = {};
editor.remove();
editorRef.current = undefined;
}
tinymce.init(finalInit);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
React.useEffect(function () {
if (editorRef.current) {
bindHandlers();
}
_this.id = _this.props.id || uuid('tiny-react');
_this.elementRef = React.createRef();
_this.inline = (_c = (_a = _this.props.inline) !== null && _a !== void 0 ? _a : (_b = _this.props.init) === null || _b === void 0 ? void 0 : _b.inline) !== null && _c !== void 0 ? _c : false;
_this.boundHandlers = {};
return _this;
}
Object.defineProperty(Editor.prototype, "view", {
get: function () {
var _a, _b;
return (_b = (_a = this.elementRef.current) === null || _a === void 0 ? void 0 : _a.ownerDocument.defaultView) !== null && _b !== void 0 ? _b : window;
},
enumerable: false,
configurable: true
});
React.useEffect(function () {
var _a;
if (rollbackTimerRef.current) {
clearTimeout(rollbackTimerRef.current);
rollbackTimerRef.current = undefined;
Editor.prototype.componentDidUpdate = function (prevProps) {
var _this = this;
var _a, _b;
if (this.rollbackTimer) {
clearTimeout(this.rollbackTimer);
this.rollbackTimer = undefined;
}
if (editorRef.current) {
if (editorRef.current.initialized) {
var editorContent = editorRef.current.getContent();
if (typeof initialValue === 'string') {
if (this.editor) {
this.bindHandlers(prevProps);
if (this.editor.initialized) {
this.currentContent = (_a = this.currentContent) !== null && _a !== void 0 ? _a : this.editor.getContent();
if (typeof this.props.initialValue === 'string' && this.props.initialValue !== prevProps.initialValue) {
// same as resetContent in TinyMCE 5
editorRef.current.setContent(initialValue);
editorRef.current.undoManager.clear();
editorRef.current.undoManager.add();
editorRef.current.setDirty(false);
this.editor.setContent(this.props.initialValue);
this.editor.undoManager.clear();
this.editor.undoManager.add();
this.editor.setDirty(false);
}
else if (typeof value === 'string' && value !== editorContent) {
var localEditor_1 = editorRef.current;
else if (typeof this.props.value === 'string' && this.props.value !== this.currentContent) {
var localEditor_1 = this.editor;
localEditor_1.undoManager.transact(function () {

@@ -326,3 +214,3 @@ // inline editors grab focus when restoring selection

var cursor;
if (!inline || localEditor_1.hasFocus()) {
if (!_this.inline || localEditor_1.hasFocus()) {
try {

@@ -335,4 +223,5 @@ // getBookmark throws exceptions when the editor has not been focused

}
localEditor_1.setContent(value);
if (!inline || localEditor_1.hasFocus()) {
var valueCursor = _this.valueCursor;
localEditor_1.setContent(_this.props.value);
if (!_this.inline || localEditor_1.hasFocus()) {
for (var _i = 0, _a = [cursor, valueCursor]; _i < _a.length; _i++) {

@@ -343,3 +232,3 @@ var bookmark = _a[_i];

localEditor_1.selection.moveToBookmark(bookmark);
setValueCursor(bookmark);
_this.valueCursor = bookmark;
break;

@@ -353,11 +242,146 @@ }

}
if (props.disabled) {
var disabled = (_a = props.disabled) !== null && _a !== void 0 ? _a : false;
setMode(editorRef.current, disabled ? 'readonly' : 'design');
if (this.props.disabled !== prevProps.disabled) {
var disabled = (_b = this.props.disabled) !== null && _b !== void 0 ? _b : false;
setMode(this.editor, disabled ? 'readonly' : 'design');
}
}
}
}, [initialValue, inline, props.disabled, value, valueCursor]);
return inline ? renderInline() : renderIframe();
};
Editor.propTypes = EditorPropTypes;
};
Editor.prototype.componentDidMount = function () {
var _this = this;
var _a, _b, _c, _d, _e;
if (getTinymce(this.view) !== null) {
this.initialise();
}
else if (Array.isArray(this.props.tinymceScriptSrc) && this.props.tinymceScriptSrc.length === 0) {
(_b = (_a = this.props).onScriptsLoadError) === null || _b === void 0 ? void 0 : _b.call(_a, new Error('No `tinymce` global is present but the `tinymceScriptSrc` prop was an empty array.'));
}
else if ((_c = this.elementRef.current) === null || _c === void 0 ? void 0 : _c.ownerDocument) {
var successHandler = function () {
var _a, _b;
(_b = (_a = _this.props).onScriptsLoad) === null || _b === void 0 ? void 0 : _b.call(_a);
_this.initialise();
};
var errorHandler = function (err) {
var _a, _b;
(_b = (_a = _this.props).onScriptsLoadError) === null || _b === void 0 ? void 0 : _b.call(_a, err);
};
ScriptLoader.loadList(this.elementRef.current.ownerDocument, this.getScriptSources(), (_e = (_d = this.props.scriptLoading) === null || _d === void 0 ? void 0 : _d.delay) !== null && _e !== void 0 ? _e : 0, successHandler, errorHandler);
}
};
Editor.prototype.componentWillUnmount = function () {
var _this = this;
var editor = this.editor;
if (editor) {
editor.off(this.changeEvents(), this.handleEditorChange);
editor.off(this.beforeInputEvent(), this.handleBeforeInput);
editor.off('keypress', this.handleEditorChangeSpecial);
editor.off('keydown', this.handleBeforeInputSpecial);
editor.off('NewBlock', this.handleEditorChange);
Object.keys(this.boundHandlers).forEach(function (eventName) {
editor.off(eventName, _this.boundHandlers[eventName]);
});
this.boundHandlers = {};
editor.remove();
this.editor = undefined;
}
};
Editor.prototype.render = function () {
return this.inline ? this.renderInline() : this.renderIframe();
};
Editor.prototype.changeEvents = function () {
var _a, _b, _c;
var isIE = (_c = (_b = (_a = getTinymce(this.view)) === null || _a === void 0 ? void 0 : _a.Env) === null || _b === void 0 ? void 0 : _b.browser) === null || _c === void 0 ? void 0 : _c.isIE();
return (isIE
? 'change keyup compositionend setcontent CommentChange'
: 'change input compositionend setcontent CommentChange');
};
Editor.prototype.beforeInputEvent = function () {
return isBeforeInputEventAvailable() ? 'beforeinput SelectionChange' : 'SelectionChange';
};
Editor.prototype.renderInline = function () {
var _a = this.props.tagName, tagName = _a === void 0 ? 'div' : _a;
return React.createElement(tagName, {
ref: this.elementRef,
id: this.id,
tabIndex: this.props.tabIndex
});
};
Editor.prototype.renderIframe = function () {
return React.createElement('textarea', {
ref: this.elementRef,
style: { visibility: 'hidden' },
name: this.props.textareaName,
id: this.id,
tabIndex: this.props.tabIndex
});
};
Editor.prototype.getScriptSources = function () {
var _a, _b;
var async = (_a = this.props.scriptLoading) === null || _a === void 0 ? void 0 : _a.async;
var defer = (_b = this.props.scriptLoading) === null || _b === void 0 ? void 0 : _b.defer;
if (this.props.tinymceScriptSrc !== undefined) {
if (typeof this.props.tinymceScriptSrc === 'string') {
return [{ src: this.props.tinymceScriptSrc, async: async, defer: defer }];
}
// multiple scripts can be specified which allows for hybrid mode
return this.props.tinymceScriptSrc.map(function (item) {
if (typeof item === 'string') {
// async does not make sense for multiple items unless
// they are not dependent (which will be unlikely)
return { src: item, async: async, defer: defer };
}
else {
return item;
}
});
}
// fallback to the cloud when the tinymceScriptSrc is not specified
var channel = this.props.cloudChannel; // `cloudChannel` is in `defaultProps`, so it's always defined.
var apiKey = this.props.apiKey ? this.props.apiKey : 'no-api-key';
var cloudTinyJs = "https://cdn.tiny.cloud/1/".concat(apiKey, "/tinymce/").concat(channel, "/tinymce.min.js");
return [{ src: cloudTinyJs, async: async, defer: defer }];
};
Editor.prototype.getInitialValue = function () {
if (typeof this.props.initialValue === 'string') {
return this.props.initialValue;
}
else if (typeof this.props.value === 'string') {
return this.props.value;
}
else {
return '';
}
};
Editor.prototype.bindHandlers = function (prevProps) {
var _this = this;
if (this.editor !== undefined) {
// typescript chokes trying to understand the type of the lookup function
configHandlers(this.editor, prevProps, this.props, this.boundHandlers, function (key) { return _this.props[key]; });
// check if we should monitor editor changes
var isValueControlled = function (p) { return p.onEditorChange !== undefined || p.value !== undefined; };
var wasControlled = isValueControlled(prevProps);
var nowControlled = isValueControlled(this.props);
if (!wasControlled && nowControlled) {
this.editor.on(this.changeEvents(), this.handleEditorChange);
this.editor.on(this.beforeInputEvent(), this.handleBeforeInput);
this.editor.on('keydown', this.handleBeforeInputSpecial);
this.editor.on('keyup', this.handleEditorChangeSpecial);
this.editor.on('NewBlock', this.handleEditorChange);
}
else if (wasControlled && !nowControlled) {
this.editor.off(this.changeEvents(), this.handleEditorChange);
this.editor.off(this.beforeInputEvent(), this.handleBeforeInput);
this.editor.off('keydown', this.handleBeforeInputSpecial);
this.editor.off('keyup', this.handleEditorChangeSpecial);
this.editor.off('NewBlock', this.handleEditorChange);
}
}
};
Editor.propTypes = EditorPropTypes;
Editor.defaultProps = {
cloudChannel: '7',
};
return Editor;
}(React.Component));
export { Editor };

@@ -5,3 +5,3 @@ import * as PropTypes from 'prop-types';

export type CopyProps<T> = {
[P in keyof T]: PropTypes.Requireable<any>;
[P in keyof T]: PropTypes.Requireable<unknown>;
};

@@ -8,0 +8,0 @@ export type IEventPropTypes = CopyProps<IEvents>;

@@ -1,8 +0,8 @@

import type { EditorEvent, Editor as TinyMCEEditor } from 'tinymce';
import { IEventPropTypes } from './components/EditorPropTypes';
import { IAllProps } from './components/Editor';
import { IEventPropTypes } from './components/EditorPropTypes';
import type { Editor as TinyMCEEditor, EditorEvent } from 'tinymce';
export declare const isFunction: (x: unknown) => x is Function;
type PropLookup = <K extends keyof IAllProps>(key: K) => IAllProps[K] | undefined;
export declare const configHandlers2: <H>(handlerLookup: PropLookup, on: (name: string, handler: H) => void, off: (name: string, handler: H) => void, adapter: <K extends keyof IEventPropTypes>(lookup: PropLookup, key: K) => H, props: Partial<IAllProps>, boundHandlers: Record<string, H>) => void;
export declare const configHandlers: (editor: TinyMCEEditor, props: Partial<IAllProps>, boundHandlers: Record<string, (event: EditorEvent<any>) => unknown>, lookup: PropLookup) => void;
export declare const configHandlers2: <H>(handlerLookup: PropLookup, on: (name: string, handler: H) => void, off: (name: string, handler: H) => void, adapter: <K extends keyof IEventPropTypes>(lookup: PropLookup, key: K) => H, prevProps: Partial<IAllProps>, props: Partial<IAllProps>, boundHandlers: Record<string, H>) => void;
export declare const configHandlers: (editor: TinyMCEEditor, prevProps: Partial<IAllProps>, props: Partial<IAllProps>, boundHandlers: Record<string, (event: EditorEvent<any>) => unknown>, lookup: PropLookup) => void;
export declare const uuid: (prefix: string) => string;

@@ -9,0 +9,0 @@ export declare const isTextareaOrInput: (element: Element | null) => element is (HTMLTextAreaElement | HTMLInputElement);

@@ -1,2 +0,1 @@

import { Type } from '@ephox/katamari';
import { eventPropTypes } from './components/EditorPropTypes';

@@ -6,16 +5,15 @@ export var isFunction = function (x) { return typeof x === 'function'; };

var eventAttrToEventName = function (attrName) { return attrName.substr(2); };
export var configHandlers2 = function (handlerLookup, on, off, adapter, props, boundHandlers) {
var eventKeys = Object.keys(eventPropTypes);
export var configHandlers2 = function (handlerLookup, on, off, adapter, prevProps, props, boundHandlers) {
var prevEventKeys = Object.keys(prevProps).filter(isEventProp);
var currEventKeys = Object.keys(props).filter(isEventProp);
var unboundEventKeys = eventKeys.filter(function (key) { return props[key] === undefined; });
unboundEventKeys.forEach(function (key) {
var removedKeys = prevEventKeys.filter(function (key) { return props[key] === undefined; });
var addedKeys = currEventKeys.filter(function (key) { return prevProps[key] === undefined; });
removedKeys.forEach(function (key) {
// remove event handler
var eventName = eventAttrToEventName(key);
var wrappedHandler = boundHandlers[eventName];
if (Type.isNonNullable(wrappedHandler)) {
off(eventName, wrappedHandler);
delete boundHandlers[eventName];
}
off(eventName, wrappedHandler);
delete boundHandlers[eventName];
});
currEventKeys.forEach(function (key) {
addedKeys.forEach(function (key) {
var wrappedHandler = adapter(handlerLookup, key);

@@ -27,6 +25,6 @@ var eventName = eventAttrToEventName(key);

};
export var configHandlers = function (editor, props, boundHandlers, lookup) {
export var configHandlers = function (editor, prevProps, props, boundHandlers, lookup) {
return configHandlers2(lookup, editor.on.bind(editor), editor.off.bind(editor),
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
function (handlerLookup, key) { return function (e) { var _a; return (_a = handlerLookup(key)) === null || _a === void 0 ? void 0 : _a(e, editor); }; }, props, boundHandlers);
function (handlerLookup, key) { return function (e) { var _a; return (_a = handlerLookup(key)) === null || _a === void 0 ? void 0 : _a(e, editor); }; }, prevProps, props, boundHandlers);
};

@@ -33,0 +31,0 @@ var unique = 0;

@@ -53,8 +53,8 @@ {

"@ephox/sugar": "^9.2.1",
"@storybook/addon-essentials": "^8.2.4",
"@storybook/addon-interactions": "^8.2.4",
"@storybook/addon-links": "^8.2.4",
"@storybook/blocks": "^8.2.4",
"@storybook/react": "^8.2.4",
"@storybook/react-vite": "^8.2.4",
"@storybook/addon-essentials": "^8.6.0",
"@storybook/addon-interactions": "^8.6.0",
"@storybook/addon-links": "^8.6.0",
"@storybook/blocks": "^8.6.0",
"@storybook/react": "^8.6.0",
"@storybook/react-vite": "^8.6.0",
"@tinymce/beehive-flow": "^0.19.0",

@@ -65,10 +65,9 @@ "@tinymce/eslint-plugin": "^2.3.1",

"@types/prop-types": "^15.7.12",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"eslint-plugin-react-hooks": "^5.1.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"gh-pages": "^6.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"rimraf": "^6.0.1",
"storybook": "^8.2.4",
"storybook": "^8.6.0",
"tinymce": "^7.2.1",

@@ -82,4 +81,4 @@ "tinymce-4": "npm:tinymce@^4",

},
"version": "6.0.1-feature.20250302152522429.sha857c954",
"version": "6.0.1-feature.20250303004124356.shab1f435a",
"name": "@tinymce/tinymce-react"
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc