argous 1.2.205 → 1.2.207
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Editor/Events.d.ts +9 -7
- package/dist/components/Editor/Events.js +0 -7
- package/dist/components/Editor/TinyMCE.d.ts +1 -8
- package/dist/components/Editor/TinyMCE.js +2 -10
- package/dist/components/Editor/Utils.d.ts +2 -7
- package/dist/components/Editor/Utils.js +29 -9
- package/dist/components/Editor/components/Editor.d.ts +17 -16
- package/dist/components/Editor/components/Editor.js +93 -50
- package/dist/components/Editor/components/EditorPropTypes.d.ts +1 -8
- package/dist/components/Editor/components/EditorPropTypes.js +19 -9
- package/dist/components/Editor/index.d.ts +2 -9
- package/dist/components/Editor/index.js +0 -7
- package/lib/components/Editor/Events.ts +9 -8
- package/lib/components/Editor/ScriptLoader2.ts +161 -0
- package/lib/components/Editor/TinyMCE.ts +2 -12
- package/lib/components/Editor/Utils.ts +25 -8
- package/lib/components/Editor/components/Editor.tsx +101 -78
- package/lib/components/Editor/components/EditorPropTypes.ts +21 -12
- package/lib/components/Editor/index.tsx +2 -10
- package/package.json +1 -1
- package/lib/components/Editor/ScriptLoader.ts +0 -96
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { uuid } from './Utils';
|
|
2
|
+
|
|
3
|
+
export type CallbackFn = () => void;
|
|
4
|
+
|
|
5
|
+
export interface ScriptItem {
|
|
6
|
+
src: string;
|
|
7
|
+
async?: boolean;
|
|
8
|
+
defer?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Id {
|
|
12
|
+
id: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const injectScriptTag = (doc: Document, item: ScriptItem & Id, handler: (id: string, err?: unknown) => void) => {
|
|
16
|
+
const scriptTag = doc.createElement('script');
|
|
17
|
+
scriptTag.referrerPolicy = 'origin';
|
|
18
|
+
scriptTag.type = 'application/javascript';
|
|
19
|
+
scriptTag.id = item.id;
|
|
20
|
+
scriptTag.src = item.src;
|
|
21
|
+
scriptTag.async = item.async ?? false;
|
|
22
|
+
scriptTag.defer = item.defer ?? false;
|
|
23
|
+
|
|
24
|
+
const loadHandler = () => {
|
|
25
|
+
scriptTag.removeEventListener('load', loadHandler);
|
|
26
|
+
scriptTag.removeEventListener('error', errorHandler);
|
|
27
|
+
handler(item.src);
|
|
28
|
+
};
|
|
29
|
+
const errorHandler = (err: unknown) => {
|
|
30
|
+
scriptTag.removeEventListener('load', loadHandler);
|
|
31
|
+
scriptTag.removeEventListener('error', errorHandler);
|
|
32
|
+
handler(item.src, err);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
scriptTag.addEventListener('load', loadHandler);
|
|
36
|
+
scriptTag.addEventListener('error', errorHandler);
|
|
37
|
+
|
|
38
|
+
if (doc.head) {
|
|
39
|
+
doc.head.appendChild(scriptTag);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
interface ScriptState {
|
|
44
|
+
id: string;
|
|
45
|
+
src: string;
|
|
46
|
+
done: boolean;
|
|
47
|
+
error?: unknown;
|
|
48
|
+
handlers: ((src: string, err?: unknown) => void)[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const createDocumentScriptLoader = (doc: Document) => {
|
|
52
|
+
let lookup: Record<string, ScriptState> = {};
|
|
53
|
+
|
|
54
|
+
const scriptLoadOrErrorHandler = (src: string, err?: unknown) => {
|
|
55
|
+
const item = lookup[src];
|
|
56
|
+
item.done = true;
|
|
57
|
+
item.error = err;
|
|
58
|
+
for (const h of item.handlers) {
|
|
59
|
+
h(src, err);
|
|
60
|
+
}
|
|
61
|
+
item.handlers = [];
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const loadScripts = (items: ScriptItem[], success: () => void, failure?: (err: unknown) => void) => {
|
|
65
|
+
// eslint-disable-next-line no-console
|
|
66
|
+
const failureOrLog = (err: unknown) => failure !== undefined ? failure(err) : console.error(err);
|
|
67
|
+
if (items.length === 0) {
|
|
68
|
+
failureOrLog(new Error('At least one script must be provided'));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
let successCount = 0;
|
|
72
|
+
let failed = false;
|
|
73
|
+
const loaded = (_src: string, err?: unknown) => {
|
|
74
|
+
if (failed) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (err) {
|
|
78
|
+
failed = true;
|
|
79
|
+
failureOrLog(err);
|
|
80
|
+
} else if (++successCount === items.length) {
|
|
81
|
+
success();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
for (const item of items) {
|
|
85
|
+
const existing = lookup[item.src];
|
|
86
|
+
if (existing) {
|
|
87
|
+
if (existing.done) {
|
|
88
|
+
loaded(item.src, existing.error);
|
|
89
|
+
} else {
|
|
90
|
+
existing.handlers.push(loaded);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
// create a new entry
|
|
94
|
+
const id = uuid('tiny-');
|
|
95
|
+
lookup[item.src] = {
|
|
96
|
+
id,
|
|
97
|
+
src: item.src,
|
|
98
|
+
done: false,
|
|
99
|
+
error: null,
|
|
100
|
+
handlers: [loaded],
|
|
101
|
+
};
|
|
102
|
+
injectScriptTag(doc, { id, ...item }, scriptLoadOrErrorHandler);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const deleteScripts = () => {
|
|
108
|
+
for (const item of Object.values(lookup)) {
|
|
109
|
+
const scriptTag = doc.getElementById(item.id);
|
|
110
|
+
if (scriptTag != null && scriptTag.tagName === 'SCRIPT') {
|
|
111
|
+
scriptTag.parentNode?.removeChild(scriptTag);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
lookup = {};
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const getDocument = () => doc;
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
loadScripts,
|
|
121
|
+
deleteScripts,
|
|
122
|
+
getDocument
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
type DocumentScriptLoader = ReturnType<typeof createDocumentScriptLoader>;
|
|
127
|
+
|
|
128
|
+
const createScriptLoader = () => {
|
|
129
|
+
const cache: DocumentScriptLoader[] = [];
|
|
130
|
+
|
|
131
|
+
const getDocumentScriptLoader = (doc: Document) => {
|
|
132
|
+
let loader = cache.find((l) => l.getDocument() === doc);
|
|
133
|
+
if (loader === undefined) {
|
|
134
|
+
loader = createDocumentScriptLoader(doc);
|
|
135
|
+
cache.push(loader);
|
|
136
|
+
}
|
|
137
|
+
return loader;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const loadList = (doc: Document, items: ScriptItem[], delay: number, success: () => void, failure?: (err: unknown) => void) => {
|
|
141
|
+
const doLoad = () => getDocumentScriptLoader(doc).loadScripts(items, success, failure);
|
|
142
|
+
if (delay > 0) {
|
|
143
|
+
setTimeout(doLoad, delay);
|
|
144
|
+
} else {
|
|
145
|
+
doLoad();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const reinitialize = () => {
|
|
150
|
+
for (let loader = cache.pop(); loader != null; loader = cache.pop()) {
|
|
151
|
+
loader.deleteScripts();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
loadList,
|
|
157
|
+
reinitialize
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export const ScriptLoader = createScriptLoader();
|
|
@@ -1,17 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2017-present, Ephox, Inc.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the Apache 2 license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
import { TinyMCE as TinyMCEGlobal } from './tinymce/tinymce';
|
|
10
2
|
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const getTinymce = (): TinyMCEGlobal | null => {
|
|
14
|
-
const global = getGlobal();
|
|
3
|
+
const getTinymce = (view: Window): TinyMCEGlobal | null => {
|
|
4
|
+
const global = view as any;
|
|
15
5
|
|
|
16
6
|
return global && global.tinymce ? global.tinymce : null;
|
|
17
7
|
};
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2017-present, Ephox, Inc.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the Apache 2 license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
import { IAllProps } from './components/Editor';
|
|
10
2
|
import { eventPropTypes, IEventPropTypes } from './components/EditorPropTypes';
|
|
11
3
|
import { Editor as TinyMCEEditor, EditorEvent } from './tinymce/tinymce';
|
|
@@ -60,6 +52,7 @@ export const configHandlers = (
|
|
|
60
52
|
lookup,
|
|
61
53
|
editor.on.bind(editor),
|
|
62
54
|
editor.off.bind(editor),
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
63
56
|
(handlerLookup, key) => (e) => handlerLookup(key)?.(e, editor),
|
|
64
57
|
prevProps,
|
|
65
58
|
props,
|
|
@@ -93,3 +86,27 @@ export const mergePlugins = (initPlugins: string | string[] | undefined, inputPl
|
|
|
93
86
|
|
|
94
87
|
export const isBeforeInputEventAvailable = () => window.InputEvent && typeof (InputEvent.prototype as any).getTargetRanges === 'function';
|
|
95
88
|
|
|
89
|
+
export const isInDoc = (elem: Node) => {
|
|
90
|
+
if (!('isConnected' in Node.prototype)) {
|
|
91
|
+
// Fallback for IE and old Edge
|
|
92
|
+
let current = elem;
|
|
93
|
+
let parent = elem.parentNode;
|
|
94
|
+
while (parent != null) {
|
|
95
|
+
current = parent;
|
|
96
|
+
parent = current.parentNode;
|
|
97
|
+
}
|
|
98
|
+
return current === elem.ownerDocument;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return elem.isConnected;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const setMode = (editor: TinyMCEEditor | undefined, mode: 'readonly' | 'design') => {
|
|
105
|
+
if (editor !== undefined) {
|
|
106
|
+
if (editor.mode != null && typeof editor.mode === 'object' && typeof editor.mode.set === 'function') {
|
|
107
|
+
editor.mode.set(mode);
|
|
108
|
+
} else { // support TinyMCE 4
|
|
109
|
+
(editor as any).setMode(mode);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
@@ -1,20 +1,13 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2017-present, Ephox, Inc.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the Apache 2 license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
import { EditorPropTypes, IEditorPropTypes } from './EditorPropTypes';
|
|
10
2
|
import { IEvents } from '../Events';
|
|
11
|
-
import { ScriptLoader } from '../
|
|
3
|
+
import { ScriptItem, ScriptLoader } from '../ScriptLoader2';
|
|
12
4
|
import { getTinymce } from '../TinyMCE';
|
|
13
|
-
import { Bookmark, Editor as TinyMCEEditor, EditorEvent,
|
|
14
|
-
import { isFunction, isTextareaOrInput, mergePlugins, uuid, configHandlers, isBeforeInputEventAvailable } from '../Utils';
|
|
15
|
-
import { useTheme } from '@material-ui/core';
|
|
5
|
+
import { Bookmark, Editor as TinyMCEEditor, EditorEvent, TinyMCE } from '../tinymce/tinymce';
|
|
6
|
+
import { isFunction, isTextareaOrInput, mergePlugins, uuid, configHandlers, isBeforeInputEventAvailable, isInDoc, setMode } from '../Utils';
|
|
16
7
|
import * as React from 'react';
|
|
17
8
|
|
|
9
|
+
type EditorOptions = Parameters<TinyMCE['init']>[0];
|
|
10
|
+
|
|
18
11
|
export interface IProps {
|
|
19
12
|
apiKey: string;
|
|
20
13
|
id: string;
|
|
@@ -22,16 +15,15 @@ export interface IProps {
|
|
|
22
15
|
initialValue: string;
|
|
23
16
|
onEditorChange: (a: string, editor: TinyMCEEditor) => void;
|
|
24
17
|
value: string;
|
|
25
|
-
init:
|
|
26
|
-
/** @deprecated use `editor.getContent({format: 'text' })` in `onEditorChange` prop instead */
|
|
27
|
-
outputFormat: 'html' | 'text';
|
|
18
|
+
init: EditorOptions & { selector?: undefined; target?: undefined };
|
|
28
19
|
tagName: string;
|
|
29
20
|
cloudChannel: string;
|
|
30
|
-
plugins: NonNullable<
|
|
31
|
-
toolbar: NonNullable<
|
|
21
|
+
plugins: NonNullable<EditorOptions['plugins']>;
|
|
22
|
+
toolbar: NonNullable<EditorOptions['toolbar']>;
|
|
32
23
|
disabled: boolean;
|
|
33
24
|
textareaName: string;
|
|
34
|
-
tinymceScriptSrc: string;
|
|
25
|
+
tinymceScriptSrc: string | string[] | ScriptItem[];
|
|
26
|
+
rollback: number | false;
|
|
35
27
|
scriptLoading: {
|
|
36
28
|
async?: boolean;
|
|
37
29
|
defer?: boolean;
|
|
@@ -41,14 +33,11 @@ export interface IProps {
|
|
|
41
33
|
|
|
42
34
|
export interface IAllProps extends Partial<IProps>, Partial<IEvents> { }
|
|
43
35
|
|
|
44
|
-
const changeEvents = () => getTinymce()?.Env?.browser?.isIE() ? 'change keyup compositionend setcontent' : 'change input compositionend setcontent';
|
|
45
|
-
const beforeInputEvent = () => isBeforeInputEventAvailable() ? 'beforeinput SelectionChange' : 'SelectionChange';
|
|
46
|
-
|
|
47
36
|
export class Editor extends React.Component<IAllProps> {
|
|
48
37
|
public static propTypes: IEditorPropTypes = EditorPropTypes;
|
|
49
38
|
|
|
50
39
|
public static defaultProps: Partial<IAllProps> = {
|
|
51
|
-
cloudChannel: '
|
|
40
|
+
cloudChannel: '6'
|
|
52
41
|
};
|
|
53
42
|
|
|
54
43
|
public editor?: TinyMCEEditor;
|
|
@@ -69,6 +58,10 @@ export class Editor extends React.Component<IAllProps> {
|
|
|
69
58
|
this.boundHandlers = {};
|
|
70
59
|
}
|
|
71
60
|
|
|
61
|
+
private get view() {
|
|
62
|
+
return this.elementRef.current?.ownerDocument.defaultView ?? window;
|
|
63
|
+
}
|
|
64
|
+
|
|
72
65
|
public componentDidUpdate(prevProps: Partial<IAllProps>) {
|
|
73
66
|
if (this.rollbackTimer) {
|
|
74
67
|
clearTimeout(this.rollbackTimer);
|
|
@@ -114,23 +107,31 @@ export class Editor extends React.Component<IAllProps> {
|
|
|
114
107
|
}
|
|
115
108
|
if (this.props.disabled !== prevProps.disabled) {
|
|
116
109
|
const disabled = this.props.disabled ?? false;
|
|
117
|
-
this.editor
|
|
110
|
+
setMode(this.editor, disabled ? 'readonly' : 'design');
|
|
118
111
|
}
|
|
119
112
|
}
|
|
120
113
|
}
|
|
121
114
|
}
|
|
122
115
|
|
|
123
116
|
public componentDidMount() {
|
|
124
|
-
if (getTinymce() !== null) {
|
|
117
|
+
if (getTinymce(this.view) !== null) {
|
|
125
118
|
this.initialise();
|
|
126
|
-
} else if (this.
|
|
127
|
-
|
|
119
|
+
} else if (Array.isArray(this.props.tinymceScriptSrc) && this.props.tinymceScriptSrc.length === 0) {
|
|
120
|
+
this.props.onScriptsLoadError?.(new Error('No `tinymce` global is present but the `tinymceScriptSrc` prop was an empty array.'));
|
|
121
|
+
} else if (this.elementRef.current?.ownerDocument) {
|
|
122
|
+
const successHandler = () => {
|
|
123
|
+
this.props.onScriptsLoad?.();
|
|
124
|
+
this.initialise();
|
|
125
|
+
};
|
|
126
|
+
const errorHandler = (err: unknown) => {
|
|
127
|
+
this.props.onScriptsLoadError?.(err);
|
|
128
|
+
};
|
|
129
|
+
ScriptLoader.loadList(
|
|
128
130
|
this.elementRef.current.ownerDocument,
|
|
129
|
-
this.
|
|
130
|
-
this.props.scriptLoading?.async ?? false,
|
|
131
|
-
this.props.scriptLoading?.defer ?? false,
|
|
131
|
+
this.getScriptSources(),
|
|
132
132
|
this.props.scriptLoading?.delay ?? 0,
|
|
133
|
-
|
|
133
|
+
successHandler,
|
|
134
|
+
errorHandler
|
|
134
135
|
);
|
|
135
136
|
}
|
|
136
137
|
}
|
|
@@ -138,8 +139,8 @@ export class Editor extends React.Component<IAllProps> {
|
|
|
138
139
|
public componentWillUnmount() {
|
|
139
140
|
const editor = this.editor;
|
|
140
141
|
if (editor) {
|
|
141
|
-
editor.off(changeEvents(), this.handleEditorChange);
|
|
142
|
-
editor.off(beforeInputEvent(), this.handleBeforeInput);
|
|
142
|
+
editor.off(this.changeEvents(), this.handleEditorChange);
|
|
143
|
+
editor.off(this.beforeInputEvent(), this.handleBeforeInput);
|
|
143
144
|
editor.off('keypress', this.handleEditorChangeSpecial);
|
|
144
145
|
editor.off('keydown', this.handleBeforeInputSpecial);
|
|
145
146
|
editor.off('NewBlock', this.handleEditorChange);
|
|
@@ -153,11 +154,19 @@ export class Editor extends React.Component<IAllProps> {
|
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
public render() {
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
return this.inline ? this.renderInline() : this.renderIframe();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private changeEvents() {
|
|
161
|
+
const isIE = getTinymce(this.view)?.Env?.browser?.isIE();
|
|
162
|
+
return (isIE
|
|
163
|
+
? 'change keyup compositionend setcontent CommentChange'
|
|
164
|
+
: 'change input compositionend setcontent CommentChange'
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private beforeInputEvent() {
|
|
169
|
+
return isBeforeInputEventAvailable() ? 'beforeinput SelectionChange' : 'SelectionChange';
|
|
161
170
|
}
|
|
162
171
|
|
|
163
172
|
private renderInline() {
|
|
@@ -178,14 +187,29 @@ export class Editor extends React.Component<IAllProps> {
|
|
|
178
187
|
});
|
|
179
188
|
}
|
|
180
189
|
|
|
181
|
-
private
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
190
|
+
private getScriptSources(): ScriptItem[] {
|
|
191
|
+
const async = this.props.scriptLoading?.async;
|
|
192
|
+
const defer = this.props.scriptLoading?.defer;
|
|
193
|
+
if (this.props.tinymceScriptSrc !== undefined) {
|
|
194
|
+
if (typeof this.props.tinymceScriptSrc === 'string') {
|
|
195
|
+
return [{ src: this.props.tinymceScriptSrc, async, defer }];
|
|
196
|
+
}
|
|
197
|
+
// multiple scripts can be specified which allows for hybrid mode
|
|
198
|
+
return this.props.tinymceScriptSrc.map((item) => {
|
|
199
|
+
if (typeof item === 'string') {
|
|
200
|
+
// async does not make sense for multiple items unless
|
|
201
|
+
// they are not dependent (which will be unlikely)
|
|
202
|
+
return { src: item, async, defer };
|
|
203
|
+
}
|
|
204
|
+
return item;
|
|
205
|
+
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
// fallback to the cloud when the tinymceScriptSrc is not specified
|
|
185
209
|
const channel = this.props.cloudChannel;
|
|
186
210
|
const apiKey = this.props.apiKey ? this.props.apiKey : 'no-api-key';
|
|
187
|
-
|
|
188
|
-
return
|
|
211
|
+
const cloudTinyJs = `https://cdn.tiny.cloud/1/${apiKey}/tinymce/${channel}/tinymce.min.js`;
|
|
212
|
+
return [{ src: cloudTinyJs, async, defer }];
|
|
189
213
|
}
|
|
190
214
|
|
|
191
215
|
private getInitialValue() {
|
|
@@ -201,20 +225,20 @@ export class Editor extends React.Component<IAllProps> {
|
|
|
201
225
|
private bindHandlers(prevProps: Partial<IAllProps>) {
|
|
202
226
|
if (this.editor !== undefined) {
|
|
203
227
|
// typescript chokes trying to understand the type of the lookup function
|
|
204
|
-
configHandlers(this.editor
|
|
228
|
+
configHandlers(this.editor, prevProps, this.props, this.boundHandlers, (key) => this.props[key] as any);
|
|
205
229
|
// check if we should monitor editor changes
|
|
206
230
|
const isValueControlled = (p: Partial<IAllProps>) => p.onEditorChange !== undefined || p.value !== undefined;
|
|
207
231
|
const wasControlled = isValueControlled(prevProps);
|
|
208
232
|
const nowControlled = isValueControlled(this.props);
|
|
209
233
|
if (!wasControlled && nowControlled) {
|
|
210
|
-
this.editor.on(changeEvents(), this.handleEditorChange);
|
|
211
|
-
this.editor.on(beforeInputEvent(), this.handleBeforeInput);
|
|
234
|
+
this.editor.on(this.changeEvents(), this.handleEditorChange);
|
|
235
|
+
this.editor.on(this.beforeInputEvent(), this.handleBeforeInput);
|
|
212
236
|
this.editor.on('keydown', this.handleBeforeInputSpecial);
|
|
213
237
|
this.editor.on('keyup', this.handleEditorChangeSpecial);
|
|
214
238
|
this.editor.on('NewBlock', this.handleEditorChange);
|
|
215
239
|
} else if (wasControlled && !nowControlled) {
|
|
216
|
-
this.editor.off(changeEvents(), this.handleEditorChange);
|
|
217
|
-
this.editor.off(beforeInputEvent(), this.handleBeforeInput);
|
|
240
|
+
this.editor.off(this.changeEvents(), this.handleEditorChange);
|
|
241
|
+
this.editor.off(this.beforeInputEvent(), this.handleBeforeInput);
|
|
218
242
|
this.editor.off('keydown', this.handleBeforeInputSpecial);
|
|
219
243
|
this.editor.off('keyup', this.handleEditorChangeSpecial);
|
|
220
244
|
this.editor.off('NewBlock', this.handleEditorChange);
|
|
@@ -262,19 +286,20 @@ export class Editor extends React.Component<IAllProps> {
|
|
|
262
286
|
const editor = this.editor;
|
|
263
287
|
if (editor && editor.initialized) {
|
|
264
288
|
const newContent = editor.getContent();
|
|
265
|
-
if (this.props.value !== undefined && this.props.value !== newContent) {
|
|
289
|
+
if (this.props.value !== undefined && this.props.value !== newContent && this.props.rollback !== false) {
|
|
266
290
|
// start a timer and revert to the value if not applied in time
|
|
267
291
|
if (!this.rollbackTimer) {
|
|
268
|
-
this.rollbackTimer = window.setTimeout(
|
|
292
|
+
this.rollbackTimer = window.setTimeout(
|
|
293
|
+
this.rollbackChange,
|
|
294
|
+
typeof this.props.rollback === 'number' ? this.props.rollback : 200
|
|
295
|
+
);
|
|
269
296
|
}
|
|
270
297
|
}
|
|
271
298
|
|
|
272
299
|
if (newContent !== this.currentContent) {
|
|
273
300
|
this.currentContent = newContent;
|
|
274
301
|
if (isFunction(this.props.onEditorChange)) {
|
|
275
|
-
|
|
276
|
-
const out = format === 'html' ? newContent : editor.getContent({ format });
|
|
277
|
-
this.props.onEditorChange(out, editor);
|
|
302
|
+
this.props.onEditorChange(newContent, editor);
|
|
278
303
|
}
|
|
279
304
|
}
|
|
280
305
|
}
|
|
@@ -286,25 +311,41 @@ export class Editor extends React.Component<IAllProps> {
|
|
|
286
311
|
}
|
|
287
312
|
};
|
|
288
313
|
|
|
289
|
-
private initialise = () => {
|
|
314
|
+
private initialise = (attempts = 0) => {
|
|
290
315
|
const target = this.elementRef.current;
|
|
291
316
|
if (!target) {
|
|
292
317
|
return; // Editor has been unmounted
|
|
293
318
|
}
|
|
319
|
+
if (!isInDoc(target)) {
|
|
320
|
+
// this is probably someone trying to help by rendering us offscreen
|
|
321
|
+
// but we can't do that because the editor iframe must be in the document
|
|
322
|
+
// in order to have state
|
|
323
|
+
if (attempts === 0) {
|
|
324
|
+
// we probably just need to wait for the current events to be processed
|
|
325
|
+
setTimeout(() => this.initialise(1), 1);
|
|
326
|
+
} else if (attempts < 100) {
|
|
327
|
+
// wait for ten seconds, polling every tenth of a second
|
|
328
|
+
setTimeout(() => this.initialise(attempts + 1), 100);
|
|
329
|
+
} else {
|
|
330
|
+
// give up, at this point it seems that more polling is unlikely to help
|
|
331
|
+
throw new Error('tinymce can only be initialised when in a document');
|
|
332
|
+
}
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
294
335
|
|
|
295
|
-
const tinymce = getTinymce();
|
|
336
|
+
const tinymce = getTinymce(this.view);
|
|
296
337
|
if (!tinymce) {
|
|
297
338
|
throw new Error('tinymce should have been loaded into global scope');
|
|
298
339
|
}
|
|
299
340
|
|
|
300
|
-
const finalInit:
|
|
341
|
+
const finalInit: EditorOptions = {
|
|
301
342
|
...this.props.init,
|
|
302
343
|
selector: undefined,
|
|
303
344
|
target,
|
|
304
345
|
readonly: this.props.disabled,
|
|
305
346
|
inline: this.inline,
|
|
306
|
-
plugins: mergePlugins(this.props.init
|
|
307
|
-
toolbar: this.props.toolbar
|
|
347
|
+
plugins: mergePlugins(this.props.init?.plugins, this.props.plugins),
|
|
348
|
+
toolbar: this.props.toolbar ?? this.props.init?.toolbar,
|
|
308
349
|
setup: (editor) => {
|
|
309
350
|
this.editor = editor;
|
|
310
351
|
this.bindHandlers({});
|
|
@@ -338,7 +379,7 @@ export class Editor extends React.Component<IAllProps> {
|
|
|
338
379
|
editor.setDirty(false);
|
|
339
380
|
}
|
|
340
381
|
const disabled = this.props.disabled ?? false;
|
|
341
|
-
editor
|
|
382
|
+
setMode(this.editor, disabled ? 'readonly' : 'design');
|
|
342
383
|
// ensure existing init_instance_callback is called
|
|
343
384
|
if (this.props.init && isFunction(this.props.init.init_instance_callback)) {
|
|
344
385
|
this.props.init.init_instance_callback(editor);
|
|
@@ -352,24 +393,6 @@ export class Editor extends React.Component<IAllProps> {
|
|
|
352
393
|
target.value = this.getInitialValue();
|
|
353
394
|
}
|
|
354
395
|
|
|
355
|
-
tinymce.init(finalInit
|
|
396
|
+
tinymce.init(finalInit);
|
|
356
397
|
};
|
|
357
398
|
}
|
|
358
|
-
|
|
359
|
-
const Tema = ({ children }) => {
|
|
360
|
-
|
|
361
|
-
const theme = useTheme().palette.type;
|
|
362
|
-
|
|
363
|
-
return (
|
|
364
|
-
<div>
|
|
365
|
-
{theme === 'dark' ? <>
|
|
366
|
-
<link rel='stylesheet' href='./skins/ui/dark/skin.min.css' />
|
|
367
|
-
<link rel='stylesheet' href='./skins/ui/dark/content.min.css' />
|
|
368
|
-
</> : <>
|
|
369
|
-
<link rel='stylesheet' href='./skins/ui/default/content.min.css' />
|
|
370
|
-
<link rel='stylesheet' href='./skins/ui/default/skin.min.css' />
|
|
371
|
-
</>}
|
|
372
|
-
{children}
|
|
373
|
-
</div>
|
|
374
|
-
);
|
|
375
|
-
};
|
|
@@ -1,16 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) 2017-present, Ephox, Inc.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the Apache 2 license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
import { IProps } from './Editor';
|
|
10
2
|
import { IEvents } from '../Events';
|
|
11
3
|
import * as PropTypes from 'prop-types';
|
|
12
4
|
|
|
13
|
-
export type CopyProps<T> = { [P in keyof T]: PropTypes.Requireable<
|
|
5
|
+
export type CopyProps<T> = { [P in keyof T]: PropTypes.Requireable<unknown> };
|
|
14
6
|
|
|
15
7
|
export type IEventPropTypes = CopyProps<IEvents>;
|
|
16
8
|
|
|
@@ -30,6 +22,7 @@ export const eventPropTypes: IEventPropTypes = {
|
|
|
30
22
|
onClearUndos: PropTypes.func,
|
|
31
23
|
onClick: PropTypes.func,
|
|
32
24
|
onContextMenu: PropTypes.func,
|
|
25
|
+
onCommentChange: PropTypes.func,
|
|
33
26
|
onCopy: PropTypes.func,
|
|
34
27
|
onCut: PropTypes.func,
|
|
35
28
|
onDblclick: PropTypes.func,
|
|
@@ -78,7 +71,15 @@ export const eventPropTypes: IEventPropTypes = {
|
|
|
78
71
|
onShow: PropTypes.func,
|
|
79
72
|
onSubmit: PropTypes.func,
|
|
80
73
|
onUndo: PropTypes.func,
|
|
81
|
-
onVisualAid: PropTypes.func
|
|
74
|
+
onVisualAid: PropTypes.func,
|
|
75
|
+
onSkinLoadError: PropTypes.func,
|
|
76
|
+
onThemeLoadError: PropTypes.func,
|
|
77
|
+
onModelLoadError: PropTypes.func,
|
|
78
|
+
onPluginLoadError: PropTypes.func,
|
|
79
|
+
onIconsLoadError: PropTypes.func,
|
|
80
|
+
onLanguageLoadError: PropTypes.func,
|
|
81
|
+
onScriptsLoad: PropTypes.func,
|
|
82
|
+
onScriptsLoadError: PropTypes.func,
|
|
82
83
|
};
|
|
83
84
|
|
|
84
85
|
export const EditorPropTypes: IEditorPropTypes = {
|
|
@@ -88,7 +89,6 @@ export const EditorPropTypes: IEditorPropTypes = {
|
|
|
88
89
|
init: PropTypes.object,
|
|
89
90
|
initialValue: PropTypes.string,
|
|
90
91
|
onEditorChange: PropTypes.func,
|
|
91
|
-
outputFormat: PropTypes.oneOf(['html', 'text']),
|
|
92
92
|
value: PropTypes.string,
|
|
93
93
|
tagName: PropTypes.string,
|
|
94
94
|
cloudChannel: PropTypes.string,
|
|
@@ -96,7 +96,16 @@ export const EditorPropTypes: IEditorPropTypes = {
|
|
|
96
96
|
toolbar: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
|
97
97
|
disabled: PropTypes.bool,
|
|
98
98
|
textareaName: PropTypes.string,
|
|
99
|
-
tinymceScriptSrc: PropTypes.
|
|
99
|
+
tinymceScriptSrc: PropTypes.oneOfType([
|
|
100
|
+
PropTypes.string,
|
|
101
|
+
PropTypes.arrayOf(PropTypes.string),
|
|
102
|
+
PropTypes.arrayOf(PropTypes.shape({
|
|
103
|
+
src: PropTypes.string,
|
|
104
|
+
async: PropTypes.bool,
|
|
105
|
+
defer: PropTypes.bool
|
|
106
|
+
}))
|
|
107
|
+
]),
|
|
108
|
+
rollback: PropTypes.oneOfType([PropTypes.number, PropTypes.oneOf([false])]),
|
|
100
109
|
scriptLoading: PropTypes.shape({
|
|
101
110
|
async: PropTypes.bool,
|
|
102
111
|
defer: PropTypes.bool,
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
* Copyright (c) 2017-present, Ephox, Inc.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the Apache 2 license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*
|
|
7
|
-
*/
|
|
1
|
+
import { Editor, IAllProps } from './components/Editor';
|
|
8
2
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export { Editor };
|
|
3
|
+
export { Editor, IAllProps };
|