@tinymce/tinymce-react
Advanced tools
Comparing version 3.10.0-rc.20210201062216249.e301a07 to 3.10.0-rc.20210319032559455.eabd99b
@@ -7,2 +7,19 @@ # Change log | ||
## [3.10.4] - 2021-03-10 | ||
### Fixed | ||
- Check for editor changes on `"compositionend"` event to more accurately trigger `onEditorChange`. INT-2348 | ||
- Updated dependencies to latest available | ||
## [3.10.3] - 2021-03-04 | ||
### Fixed | ||
- Updated dependencies to latest available | ||
## [3.10.2] - 2021-02-20 | ||
## Fixed | ||
- Event handlers are registered at setup time so props like `onBeforeRenderUI` will now be called. #INT-2325 | ||
## [3.10.1] - 2021-02-01 | ||
### Fixed | ||
- Fixed CI build | ||
## [3.10.0] - 2021-02-01 | ||
@@ -9,0 +26,0 @@ ### Fixed |
@@ -42,5 +42,5 @@ /** | ||
static defaultProps: Partial<IAllProps>; | ||
editor?: TinyMCEEditor; | ||
private id; | ||
private elementRef; | ||
private editor?; | ||
private inline; | ||
@@ -56,3 +56,3 @@ private currentContent?; | ||
id: string; | ||
}, string | ((props: any) => React.ReactElement<any, string | any | (new (props: any) => React.Component<any, any, any>)> | null) | (new (props: any) => React.Component<any, any, any>)>; | ||
}, string | React.JSXElementConstructor<any>>; | ||
private renderInline; | ||
@@ -64,4 +64,3 @@ private renderIframe; | ||
private handleEditorChange; | ||
private handleInit; | ||
private initialise; | ||
} |
@@ -17,2 +17,4 @@ "use strict"; | ||
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); | ||
@@ -49,3 +51,3 @@ function __() { this.constructor = d; } | ||
var editor = _this.editor; | ||
if (editor) { | ||
if (editor && editor.initialized) { | ||
var newContent = editor.getContent({ format: _this.props.outputFormat }); | ||
@@ -60,18 +62,2 @@ if (newContent !== _this.currentContent) { | ||
}; | ||
_this.handleInit = function (initEvent) { | ||
var editor = _this.editor; | ||
if (editor) { | ||
editor.setContent(_this.getInitialValue()); | ||
editor.undoManager.clear(); | ||
editor.undoManager.add(); | ||
editor.setDirty(false); | ||
if (Utils_1.isFunction(_this.props.onEditorChange)) { | ||
editor.on('change keyup setcontent', _this.handleEditorChange); | ||
} | ||
if (Utils_1.isFunction(_this.props.onInit)) { | ||
_this.props.onInit(initEvent, editor); | ||
} | ||
_this.bindHandlers({}); | ||
} | ||
}; | ||
_this.initialise = function () { | ||
@@ -88,3 +74,14 @@ var target = _this.elementRef.current; | ||
_this.editor = editor; | ||
editor.on('init', _this.handleInit); | ||
_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 && !Utils_1.isTextareaOrInput(target)) { | ||
editor.once('PostRender', function (_evt) { | ||
editor.setContent(_this.getInitialValue(), { no_events: true }); | ||
}); | ||
} | ||
if (_this.props.init && Utils_1.isFunction(_this.props.init.setup)) { | ||
@@ -94,5 +91,8 @@ _this.props.init.setup(editor); | ||
} }); | ||
if (Utils_1.isTextarea(_this.elementRef.current)) { | ||
_this.elementRef.current.style.visibility = ''; | ||
if (!_this.inline) { | ||
target.style.visibility = ''; | ||
} | ||
if (Utils_1.isTextareaOrInput(target)) { | ||
target.value = _this.getInitialValue(); | ||
} | ||
tinymce.init(finalInit); | ||
@@ -134,11 +134,9 @@ }; | ||
if (editor) { | ||
editor.off('init', this.handleInit); | ||
if (editor.initialized) { | ||
editor.off('change keyup setcontent', this.handleEditorChange); | ||
Object.keys(this.boundHandlers).forEach(function (eventName) { | ||
editor.off(eventName, _this.boundHandlers[eventName]); | ||
}); | ||
this.boundHandlers = {}; | ||
} | ||
editor.off('change keyup compositionend setcontent', this.handleEditorChange); | ||
Object.keys(this.boundHandlers).forEach(function (eventName) { | ||
editor.off(eventName, _this.boundHandlers[eventName]); | ||
}); | ||
this.boundHandlers = {}; | ||
editor.remove(); | ||
this.editor = undefined; | ||
} | ||
@@ -190,2 +188,12 @@ }; | ||
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('change keyup compositionend setcontent', this.handleEditorChange); | ||
} | ||
else if (wasControlled && !nowControlled) { | ||
this.editor.off('change keyup compositionend setcontent', this.handleEditorChange); | ||
} | ||
} | ||
@@ -192,0 +200,0 @@ }; |
@@ -13,7 +13,7 @@ /** | ||
declare 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 "onBeforePaste" | "onBlur" | "onClick" | "onContextMenu" | "onCopy" | "onCut" | "onDblclick" | "onDrag" | "onDragDrop" | "onDragEnd" | "onDragGesture" | "onDragOver" | "onDrop" | "onFocus" | "onFocusIn" | "onFocusOut" | "onKeyDown" | "onKeyPress" | "onKeyUp" | "onMouseDown" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onPaste" | "onSelectionChange" | "onActivate" | "onAddUndo" | "onBeforeAddUndo" | "onBeforeExecCommand" | "onBeforeGetContent" | "onBeforeRenderUI" | "onBeforeSetContent" | "onChange" | "onClearUndos" | "onDeactivate" | "onDirty" | "onExecCommand" | "onGetContent" | "onHide" | "onInit" | "onLoadContent" | "onNodeChange" | "onPostProcess" | "onPostRender" | "onPreProcess" | "onProgressState" | "onRedo" | "onRemove" | "onReset" | "onSaveContent" | "onSetAttrib" | "onObjectResizeStart" | "onObjectResized" | "onObjectSelected" | "onSetContent" | "onShow" | "onSubmit" | "onUndo" | "onVisualAid">(lookup: PropLookup, key: K) => H, prevProps: Partial<IAllProps>, props: Partial<IAllProps>, boundHandlers: Record<string, H>) => void; | ||
export declare const configHandlers2: <H>(handlerLookup: PropLookup, on: (name: string, handler: H) => void, off: (name: string, handler: H) => void, adapter: <K extends keyof import("./Events").IEvents>(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; | ||
export declare const isTextarea: (element: Element | null) => element is HTMLTextAreaElement; | ||
export declare const isTextareaOrInput: (element: Element | null) => element is HTMLInputElement | HTMLTextAreaElement; | ||
export declare const mergePlugins: (initPlugins: string | string[] | undefined, inputPlugins: string | string[] | undefined) => string[]; | ||
export {}; |
@@ -10,3 +10,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.mergePlugins = exports.isTextarea = exports.uuid = exports.configHandlers = exports.configHandlers2 = exports.isFunction = void 0; | ||
exports.mergePlugins = exports.isTextareaOrInput = exports.uuid = exports.configHandlers = exports.configHandlers2 = exports.isFunction = void 0; | ||
var EditorPropTypes_1 = require("./components/EditorPropTypes"); | ||
@@ -49,4 +49,6 @@ var isFunction = function (x) { return typeof x === 'function'; }; | ||
exports.uuid = uuid; | ||
var isTextarea = function (element) { return element !== null && element.tagName.toLowerCase() === 'textarea'; }; | ||
exports.isTextarea = isTextarea; | ||
var isTextareaOrInput = function (element) { | ||
return element !== null && (element.tagName.toLowerCase() === 'textarea' || element.tagName.toLowerCase() === 'input'); | ||
}; | ||
exports.isTextareaOrInput = isTextareaOrInput; | ||
var normalizePluginArray = function (plugins) { | ||
@@ -53,0 +55,0 @@ if (typeof plugins === 'undefined' || plugins === '') { |
@@ -42,5 +42,5 @@ /** | ||
static defaultProps: Partial<IAllProps>; | ||
editor?: TinyMCEEditor; | ||
private id; | ||
private elementRef; | ||
private editor?; | ||
private inline; | ||
@@ -56,3 +56,3 @@ private currentContent?; | ||
id: string; | ||
}, string | ((props: any) => React.ReactElement<any, string | any | (new (props: any) => React.Component<any, any, any>)> | null) | (new (props: any) => React.Component<any, any, any>)>; | ||
}, string | React.JSXElementConstructor<any>>; | ||
private renderInline; | ||
@@ -64,4 +64,3 @@ private renderIframe; | ||
private handleEditorChange; | ||
private handleInit; | ||
private initialise; | ||
} |
@@ -16,2 +16,4 @@ /** | ||
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); | ||
@@ -36,3 +38,3 @@ function __() { this.constructor = d; } | ||
import { getTinymce } from '../TinyMCE'; | ||
import { isFunction, isTextarea, mergePlugins, uuid, configHandlers } from '../Utils'; | ||
import { isFunction, isTextareaOrInput, mergePlugins, uuid, configHandlers } from '../Utils'; | ||
import { EditorPropTypes } from './EditorPropTypes'; | ||
@@ -47,3 +49,3 @@ var Editor = /** @class */ (function (_super) { | ||
var editor = _this.editor; | ||
if (editor) { | ||
if (editor && editor.initialized) { | ||
var newContent = editor.getContent({ format: _this.props.outputFormat }); | ||
@@ -58,18 +60,2 @@ if (newContent !== _this.currentContent) { | ||
}; | ||
_this.handleInit = function (initEvent) { | ||
var editor = _this.editor; | ||
if (editor) { | ||
editor.setContent(_this.getInitialValue()); | ||
editor.undoManager.clear(); | ||
editor.undoManager.add(); | ||
editor.setDirty(false); | ||
if (isFunction(_this.props.onEditorChange)) { | ||
editor.on('change keyup setcontent', _this.handleEditorChange); | ||
} | ||
if (isFunction(_this.props.onInit)) { | ||
_this.props.onInit(initEvent, editor); | ||
} | ||
_this.bindHandlers({}); | ||
} | ||
}; | ||
_this.initialise = function () { | ||
@@ -86,3 +72,14 @@ var target = _this.elementRef.current; | ||
_this.editor = editor; | ||
editor.on('init', _this.handleInit); | ||
_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)) { | ||
@@ -92,5 +89,8 @@ _this.props.init.setup(editor); | ||
} }); | ||
if (isTextarea(_this.elementRef.current)) { | ||
_this.elementRef.current.style.visibility = ''; | ||
if (!_this.inline) { | ||
target.style.visibility = ''; | ||
} | ||
if (isTextareaOrInput(target)) { | ||
target.value = _this.getInitialValue(); | ||
} | ||
tinymce.init(finalInit); | ||
@@ -132,11 +132,9 @@ }; | ||
if (editor) { | ||
editor.off('init', this.handleInit); | ||
if (editor.initialized) { | ||
editor.off('change keyup setcontent', this.handleEditorChange); | ||
Object.keys(this.boundHandlers).forEach(function (eventName) { | ||
editor.off(eventName, _this.boundHandlers[eventName]); | ||
}); | ||
this.boundHandlers = {}; | ||
} | ||
editor.off('change keyup compositionend setcontent', this.handleEditorChange); | ||
Object.keys(this.boundHandlers).forEach(function (eventName) { | ||
editor.off(eventName, _this.boundHandlers[eventName]); | ||
}); | ||
this.boundHandlers = {}; | ||
editor.remove(); | ||
this.editor = undefined; | ||
} | ||
@@ -188,2 +186,12 @@ }; | ||
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('change keyup compositionend setcontent', this.handleEditorChange); | ||
} | ||
else if (wasControlled && !nowControlled) { | ||
this.editor.off('change keyup compositionend setcontent', this.handleEditorChange); | ||
} | ||
} | ||
@@ -190,0 +198,0 @@ }; |
@@ -13,7 +13,7 @@ /** | ||
declare 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 "onBeforePaste" | "onBlur" | "onClick" | "onContextMenu" | "onCopy" | "onCut" | "onDblclick" | "onDrag" | "onDragDrop" | "onDragEnd" | "onDragGesture" | "onDragOver" | "onDrop" | "onFocus" | "onFocusIn" | "onFocusOut" | "onKeyDown" | "onKeyPress" | "onKeyUp" | "onMouseDown" | "onMouseEnter" | "onMouseLeave" | "onMouseMove" | "onMouseOut" | "onMouseOver" | "onMouseUp" | "onPaste" | "onSelectionChange" | "onActivate" | "onAddUndo" | "onBeforeAddUndo" | "onBeforeExecCommand" | "onBeforeGetContent" | "onBeforeRenderUI" | "onBeforeSetContent" | "onChange" | "onClearUndos" | "onDeactivate" | "onDirty" | "onExecCommand" | "onGetContent" | "onHide" | "onInit" | "onLoadContent" | "onNodeChange" | "onPostProcess" | "onPostRender" | "onPreProcess" | "onProgressState" | "onRedo" | "onRemove" | "onReset" | "onSaveContent" | "onSetAttrib" | "onObjectResizeStart" | "onObjectResized" | "onObjectSelected" | "onSetContent" | "onShow" | "onSubmit" | "onUndo" | "onVisualAid">(lookup: PropLookup, key: K) => H, prevProps: Partial<IAllProps>, props: Partial<IAllProps>, boundHandlers: Record<string, H>) => void; | ||
export declare const configHandlers2: <H>(handlerLookup: PropLookup, on: (name: string, handler: H) => void, off: (name: string, handler: H) => void, adapter: <K extends keyof import("./Events").IEvents>(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; | ||
export declare const isTextarea: (element: Element | null) => element is HTMLTextAreaElement; | ||
export declare const isTextareaOrInput: (element: Element | null) => element is HTMLInputElement | HTMLTextAreaElement; | ||
export declare const mergePlugins: (initPlugins: string | string[] | undefined, inputPlugins: string | string[] | undefined) => string[]; | ||
export {}; |
@@ -41,3 +41,5 @@ /** | ||
}; | ||
export var isTextarea = function (element) { return element !== null && element.tagName.toLowerCase() === 'textarea'; }; | ||
export var isTextareaOrInput = function (element) { | ||
return element !== null && (element.tagName.toLowerCase() === 'textarea' || element.tagName.toLowerCase() === 'input'); | ||
}; | ||
var normalizePluginArray = function (plugins) { | ||
@@ -44,0 +46,0 @@ if (typeof plugins === 'undefined' || plugins === '') { |
@@ -29,3 +29,3 @@ { | ||
"prop-types": "^15.6.2", | ||
"tinymce": "^5.6.2" | ||
"tinymce": "^5.7.0" | ||
}, | ||
@@ -37,20 +37,20 @@ "peerDependencies": { | ||
"devDependencies": { | ||
"@babel/core": "^7.12.10", | ||
"@ephox/agar": "^5.1.1", | ||
"@ephox/bedrock-client": "^11.0.0", | ||
"@ephox/bedrock-server": "^11.0.2", | ||
"@ephox/katamari": "^7.1.1", | ||
"@ephox/mcagar": "^5.1.1", | ||
"@ephox/sand": "^4.0.3", | ||
"@ephox/sugar": "^7.0.3", | ||
"@babel/core": "^7.13.10", | ||
"@ephox/agar": "^5.2.1", | ||
"@ephox/bedrock-client": "^11.1.1", | ||
"@ephox/bedrock-server": "^11.2.0", | ||
"@ephox/katamari": "^7.1.3", | ||
"@ephox/mcagar": "^6.0.1", | ||
"@ephox/sand": "^4.0.5", | ||
"@ephox/sugar": "^7.1.1", | ||
"@storybook/addon-info": "^5.3.21", | ||
"@storybook/react": "^6.1.11", | ||
"@storybook/react": "^6.1.21", | ||
"@storybook/storybook-deployer": "^2.8.6", | ||
"@tinymce/beehive-flow": "^0.12.0", | ||
"@tinymce/beehive-flow": "^0.14.0", | ||
"@tinymce/eslint-plugin": "^1.7.2", | ||
"@tinymce/miniature": "^3.0.1", | ||
"@types/node": "^14.14.20", | ||
"@tinymce/miniature": "^3.1.1", | ||
"@types/node": "^14.14.33", | ||
"@types/prop-types": "^15.5.8", | ||
"@types/react": "^17.0.0", | ||
"@types/react-dom": "^17.0.0", | ||
"@types/react": "^17.0.3", | ||
"@types/react-dom": "^17.0.2", | ||
"@types/storybook__addon-info": "^5.2.3", | ||
@@ -60,3 +60,3 @@ "@types/storybook__react": "^5.2.1", | ||
"babel-loader": "^8.2.2", | ||
"core-js": "^3.8.2", | ||
"core-js": "^3.9.1", | ||
"raf": "^3.4.1", | ||
@@ -68,8 +68,8 @@ "react": "^17.0.1", | ||
"tinymce-5": "npm:tinymce@^5", | ||
"ts-loader": "^8.0.14", | ||
"typescript": "^4.1.3", | ||
"webpack": "^5.12.3" | ||
"ts-loader": "^8.0.17", | ||
"typescript": "^4.2.3", | ||
"webpack": "^5.24.4" | ||
}, | ||
"version": "3.10.0-rc.20210201062216249.e301a07", | ||
"version": "3.10.0-rc.20210319032559455.eabd99b", | ||
"name": "@tinymce/tinymce-react" | ||
} |
64749
1331
Updatedtinymce@^5.7.0