@tinymce/tinymce-react
Advanced tools
Comparing version 6.0.1-feature.20250302152522429.sha857c954 to 6.0.1-feature.20250303004124356.shab1f435a
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 {}; |
"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" | ||
} |
110655
36
2028