jodit 4.16.1 → 4.17.1
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/CHANGELOG.md +21 -0
- package/es2015/jodit.css +1 -1
- package/es2015/jodit.fat.min.js +7 -7
- package/es2015/jodit.js +173 -34
- package/es2015/jodit.min.js +7 -7
- package/es2015/plugins/debug/debug.css +1 -1
- package/es2015/plugins/debug/debug.js +1 -1
- package/es2015/plugins/debug/debug.min.js +1 -1
- package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2015/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es2015/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es2018/jodit.fat.min.js +7 -7
- package/es2018/jodit.min.js +7 -7
- package/es2018/plugins/debug/debug.min.js +1 -1
- package/es2018/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es2021/jodit.css +1 -1
- package/es2021/jodit.fat.min.js +8 -8
- package/es2021/jodit.js +165 -33
- package/es2021/jodit.min.js +8 -8
- package/es2021/plugins/debug/debug.css +1 -1
- package/es2021/plugins/debug/debug.js +1 -1
- package/es2021/plugins/debug/debug.min.js +1 -1
- package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2021/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es2021/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es2021.en/jodit.css +1 -1
- package/es2021.en/jodit.fat.min.js +8 -8
- package/es2021.en/jodit.js +165 -33
- package/es2021.en/jodit.min.js +8 -8
- package/es2021.en/plugins/debug/debug.css +1 -1
- package/es2021.en/plugins/debug/debug.js +1 -1
- package/es2021.en/plugins/debug/debug.min.js +1 -1
- package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2021.en/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es5/jodit.css +2 -2
- package/es5/jodit.fat.min.js +2 -2
- package/es5/jodit.js +182 -34
- package/es5/jodit.min.css +2 -2
- package/es5/jodit.min.js +2 -2
- package/es5/plugins/debug/debug.css +1 -1
- package/es5/plugins/debug/debug.js +1 -1
- package/es5/plugins/debug/debug.min.js +1 -1
- package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es5/plugins/speech-recognize/speech-recognize.js +1 -1
- package/es5/plugins/speech-recognize/speech-recognize.min.js +1 -1
- package/es5/polyfills.fat.min.js +1 -1
- package/es5/polyfills.js +1 -1
- package/es5/polyfills.min.js +1 -1
- package/esm/config.d.ts +40 -1
- package/esm/core/constants.js +1 -1
- package/esm/core/helpers/html/safe-html.d.ts +1 -1
- package/esm/core/helpers/html/safe-html.js +8 -1
- package/esm/modules/widget/file-selector/file-selector.d.ts +10 -1
- package/esm/modules/widget/file-selector/file-selector.js +30 -18
- package/esm/plugins/drag-and-drop/drag-and-drop.js +16 -4
- package/esm/plugins/file/file.d.ts +24 -1
- package/esm/plugins/file/file.js +17 -1
- package/esm/plugins/inline-popup/config/items/img.js +22 -1
- package/esm/plugins/source/editor/engines/ace.js +2 -1
- package/esm/plugins/source/source.d.ts +17 -0
- package/esm/plugins/source/source.js +60 -8
- package/esm/plugins/video/config.d.ts +21 -1
- package/esm/plugins/video/config.js +22 -2
- package/package.json +1 -1
- package/types/config.d.ts +40 -1
- package/types/core/helpers/html/safe-html.d.ts +1 -1
- package/types/modules/widget/file-selector/file-selector.d.ts +10 -1
- package/types/plugins/file/file.d.ts +24 -1
- package/types/plugins/source/source.d.ts +17 -0
- package/types/plugins/video/config.d.ts +21 -1
package/es5/polyfills.fat.min.js
CHANGED
package/es5/polyfills.js
CHANGED
package/es5/polyfills.min.js
CHANGED
package/esm/config.d.ts
CHANGED
|
@@ -8,9 +8,10 @@ import type { HTMLTagNames } from "./types/index";
|
|
|
8
8
|
import type { AiAssistantSettings } from "./plugins/ai-assistant/interface";
|
|
9
9
|
import type { IDictionary } from "./types/index";
|
|
10
10
|
import type { Nullable } from "./types/index";
|
|
11
|
+
import type { IJodit } from "./types/index";
|
|
12
|
+
import type { IUploaderData } from "./types/index";
|
|
11
13
|
import type { ImagePropertiesOptions } from "./plugins/image-properties/interface";
|
|
12
14
|
import type { IControlType } from "./types/index";
|
|
13
|
-
import type { IJodit } from "./types/index";
|
|
14
15
|
import type { formTemplate } from "./plugins/link/template";
|
|
15
16
|
import type { IUIForm } from "./types/index";
|
|
16
17
|
import type { IUIOption } from "./types/index";
|
|
@@ -1264,6 +1265,27 @@ interface Config {
|
|
|
1264
1265
|
blockLimits: IDictionary<1>;
|
|
1265
1266
|
};
|
|
1266
1267
|
}
|
|
1268
|
+
interface Config {
|
|
1269
|
+
file: {
|
|
1270
|
+
/**
|
|
1271
|
+
* Inserts the files uploaded from the file popup's Upload tab. By default
|
|
1272
|
+
* each one goes in as a link, an image as well, the way
|
|
1273
|
+
* `uploader.defaultHandlerSuccess` inserts a file that isn't an image.
|
|
1274
|
+
* ```javascript
|
|
1275
|
+
* Jodit.make('#editor', {
|
|
1276
|
+
* file: {
|
|
1277
|
+
* defaultHandlerSuccess(data) {
|
|
1278
|
+
* data.files.forEach(file => {
|
|
1279
|
+
* this.s.insertHTML(`<a class="file" href="${data.baseurl + file}">${file}</a>`);
|
|
1280
|
+
* });
|
|
1281
|
+
* }
|
|
1282
|
+
* }
|
|
1283
|
+
* });
|
|
1284
|
+
* ```
|
|
1285
|
+
*/
|
|
1286
|
+
defaultHandlerSuccess?: (this: IJodit, data: IUploaderData) => void;
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1267
1289
|
interface Config {
|
|
1268
1290
|
autofocus: boolean;
|
|
1269
1291
|
cursorAfterAutofocus: 'start' | 'end';
|
|
@@ -1971,6 +1993,23 @@ interface Config {
|
|
|
1971
1993
|
* Default height for video iframe. Default: 345
|
|
1972
1994
|
*/
|
|
1973
1995
|
defaultHeight?: number;
|
|
1996
|
+
/**
|
|
1997
|
+
* Inserts the videos uploaded from the popup's Upload tab, which is shown
|
|
1998
|
+
* when `uploader.url` or `uploader.customUploadFunction` is set. By default
|
|
1999
|
+
* each one goes in as `<video controls src="…">`.
|
|
2000
|
+
* ```javascript
|
|
2001
|
+
* Jodit.make('#editor', {
|
|
2002
|
+
* video: {
|
|
2003
|
+
* defaultHandlerSuccess(data) {
|
|
2004
|
+
* data.files.forEach(file => {
|
|
2005
|
+
* this.s.insertHTML(`<video controls preload="metadata" src="${data.baseurl + file}"></video>`);
|
|
2006
|
+
* });
|
|
2007
|
+
* }
|
|
2008
|
+
* }
|
|
2009
|
+
* });
|
|
2010
|
+
* ```
|
|
2011
|
+
*/
|
|
2012
|
+
defaultHandlerSuccess?: (this: IJodit, data: IUploaderData) => void;
|
|
1974
2013
|
};
|
|
1975
2014
|
}
|
|
1976
2015
|
interface Config {
|
package/esm/core/constants.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
4
4
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
5
5
|
*/
|
|
6
|
-
export const APP_VERSION = "4.
|
|
6
|
+
export const APP_VERSION = "4.17.1";
|
|
7
7
|
// prettier-ignore
|
|
8
8
|
export const ES = "es2020";
|
|
9
9
|
export const IS_ES_MODERN = true;
|
|
@@ -13,4 +13,4 @@ export type safeOptions = {
|
|
|
13
13
|
* Removes dangerous constructs from HTML
|
|
14
14
|
*/
|
|
15
15
|
export declare function safeHTML(box: HTMLElement | DocumentFragment, options: safeOptions): void;
|
|
16
|
-
export declare function sanitizeHTMLElement(elm: Element | DocumentFragment, { safeJavaScriptLink, removeOnError }?: safeOptions): boolean;
|
|
16
|
+
export declare function sanitizeHTMLElement(elm: Element | DocumentFragment, { safeJavaScriptLink, removeOnError, removeEventAttributes }?: safeOptions): boolean;
|
|
@@ -145,7 +145,7 @@ function isDangerousUrl(value, tagName) {
|
|
|
145
145
|
}
|
|
146
146
|
return (/^data:image\/svg/.test(normalized) && DOCUMENT_EMBED_TAGS.has(tagName));
|
|
147
147
|
}
|
|
148
|
-
export function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } = {
|
|
148
|
+
export function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError, removeEventAttributes } = {
|
|
149
149
|
safeJavaScriptLink: true,
|
|
150
150
|
removeOnError: true
|
|
151
151
|
}) {
|
|
@@ -153,6 +153,13 @@ export function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } =
|
|
|
153
153
|
return false;
|
|
154
154
|
}
|
|
155
155
|
let effected = false;
|
|
156
|
+
// Every `on*` handler, not only `onerror`: the clean-html background
|
|
157
|
+
// pass calls this directly, and markup that reached the editable area
|
|
158
|
+
// without `safeHTML` (a drop, a script) kept its handlers otherwise.
|
|
159
|
+
// See GHSA-jhhp-r3r7-v2cg
|
|
160
|
+
if (removeEventAttributes && removeAllEventAttributes(elm)) {
|
|
161
|
+
effected = true;
|
|
162
|
+
}
|
|
156
163
|
if (removeOnError && elm.hasAttribute('onerror')) {
|
|
157
164
|
attr(elm, 'onerror', null);
|
|
158
165
|
effected = true;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
* @module modules/widget/file-selector
|
|
10
10
|
*/
|
|
11
|
-
import type { IFileBrowserCallBackData, IJodit } from "../../../types/index";
|
|
11
|
+
import type { IFileBrowserCallBackData, IJodit, IUploaderData } from "../../../types/index";
|
|
12
|
+
import { type TabOption } from "../tabs/tabs";
|
|
12
13
|
interface ImageSelectorCallbacks {
|
|
13
14
|
/**
|
|
14
15
|
* Function that will be called when the user enters the URL of the tab image and alternative text for images
|
|
@@ -34,4 +35,12 @@ interface ImageSelectorCallbacks {
|
|
|
34
35
|
* functions with different parameters
|
|
35
36
|
*/
|
|
36
37
|
export declare const FileSelectorWidget: (editor: IJodit, callbacks: ImageSelectorCallbacks, elm: HTMLElement | null, close: () => void, isImage?: boolean) => HTMLDivElement;
|
|
38
|
+
/**
|
|
39
|
+
* The Upload tab: a box that uploads the files dropped on it or chosen in its
|
|
40
|
+
* file input, then passes the uploader's answer to `onSuccess` and closes the popup
|
|
41
|
+
*/
|
|
42
|
+
export declare const UploadTab: (editor: IJodit, onSuccess: (this: IJodit, data: IUploaderData) => void, { label, accept }: {
|
|
43
|
+
label: string;
|
|
44
|
+
accept: string;
|
|
45
|
+
}) => TabOption;
|
|
37
46
|
export {};
|
|
@@ -25,28 +25,17 @@ export const FileSelectorWidget = (editor, callbacks, elm, close, isImage = true
|
|
|
25
25
|
(options.url ||
|
|
26
26
|
options.insertImageAsBase64URI ||
|
|
27
27
|
options.customUploadFunction)) {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'</div>');
|
|
33
|
-
editor.uploader.bind(dragBox, resp => {
|
|
34
|
-
const handler = isFunction(callbacks.upload)
|
|
35
|
-
? callbacks.upload
|
|
28
|
+
const { upload } = callbacks;
|
|
29
|
+
tabs.push(UploadTab(editor, resp => {
|
|
30
|
+
const handler = isFunction(upload)
|
|
31
|
+
? upload
|
|
36
32
|
: options.defaultHandlerSuccess;
|
|
37
33
|
if (isFunction(handler)) {
|
|
38
34
|
handler.call(editor, resp);
|
|
39
35
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
editor.e.fire('closeAllPopups');
|
|
44
|
-
});
|
|
45
|
-
tabs.push({
|
|
46
|
-
icon: 'upload',
|
|
47
|
-
name: 'Upload',
|
|
48
|
-
content: dragBox
|
|
49
|
-
});
|
|
36
|
+
}, isImage
|
|
37
|
+
? { label: 'Drop image', accept: 'image/*' }
|
|
38
|
+
: { label: 'Drop file', accept: '*' }));
|
|
50
39
|
}
|
|
51
40
|
if (callbacks.filebrowser) {
|
|
52
41
|
if (editor.o.filebrowser.ajax.url || editor.o.filebrowser.items.url) {
|
|
@@ -115,6 +104,29 @@ export const FileSelectorWidget = (editor, callbacks, elm, close, isImage = true
|
|
|
115
104
|
box.classList.add('jodit-file-selector');
|
|
116
105
|
return box;
|
|
117
106
|
};
|
|
107
|
+
/**
|
|
108
|
+
* The Upload tab: a box that uploads the files dropped on it or chosen in its
|
|
109
|
+
* file input, then passes the uploader's answer to `onSuccess` and closes the popup
|
|
110
|
+
*/
|
|
111
|
+
export const UploadTab = (editor, onSuccess, { label, accept }) => {
|
|
112
|
+
const dragBox = editor.c.fromHTML('<div class="jodit-drag-and-drop__file-box">' +
|
|
113
|
+
`<strong>${editor.i18n(label)}</strong>` +
|
|
114
|
+
`<span><br>${editor.i18n('or click')}</span>` +
|
|
115
|
+
`<input type="file" accept="${accept}" tabindex="-1" dir="auto" multiple=""/>` +
|
|
116
|
+
'</div>');
|
|
117
|
+
editor.uploader.bind(dragBox, resp => {
|
|
118
|
+
onSuccess.call(editor, resp);
|
|
119
|
+
editor.e.fire('closeAllPopups');
|
|
120
|
+
}, error => {
|
|
121
|
+
editor.message.error(error.message);
|
|
122
|
+
editor.e.fire('closeAllPopups');
|
|
123
|
+
});
|
|
124
|
+
return {
|
|
125
|
+
icon: 'upload',
|
|
126
|
+
name: 'Upload',
|
|
127
|
+
content: dragBox
|
|
128
|
+
};
|
|
129
|
+
};
|
|
118
130
|
function val(elm, name, value) {
|
|
119
131
|
const child = Dom.first(elm, node => Dom.isTag(node, 'input') && attr(node, 'name') === name);
|
|
120
132
|
if (!child) {
|
|
@@ -38,7 +38,13 @@ export class dragAndDrop extends Plugin {
|
|
|
38
38
|
}
|
|
39
39
|
/** @override */
|
|
40
40
|
afterInit() {
|
|
41
|
-
this.j.e
|
|
41
|
+
this.j.e
|
|
42
|
+
.on([window, this.j.ed, this.j.editor], 'dragstart.DragAndDrop', this.onDragStart)
|
|
43
|
+
// Always, not only after a `dragstart` seen in this window: a drop
|
|
44
|
+
// from another window or application never produces one here, and
|
|
45
|
+
// the browser then inserted its HTML natively, unsanitized. Routed
|
|
46
|
+
// through the paste plugin instead. See GHSA-jhhp-r3r7-v2cg
|
|
47
|
+
.on('drop.DragAndDrop', this.onDrop);
|
|
42
48
|
}
|
|
43
49
|
onDragStart(event) {
|
|
44
50
|
let target = event.target;
|
|
@@ -69,13 +75,11 @@ export class dragAndDrop extends Plugin {
|
|
|
69
75
|
addDragListeners() {
|
|
70
76
|
this.j.e
|
|
71
77
|
.on('dragover', this.onDrag)
|
|
72
|
-
.on('drop.DragAndDrop', this.onDrop)
|
|
73
78
|
.on(window, 'dragend.DragAndDrop drop.DragAndDrop mouseup.DragAndDrop', this.onDragEnd);
|
|
74
79
|
}
|
|
75
80
|
removeDragListeners() {
|
|
76
81
|
this.j.e
|
|
77
82
|
.off('dragover', this.onDrag)
|
|
78
|
-
.off('drop.DragAndDrop', this.onDrop)
|
|
79
83
|
.off(window, 'dragend.DragAndDrop drop.DragAndDrop mouseup.DragAndDrop', this.onDragEnd);
|
|
80
84
|
}
|
|
81
85
|
onDrag(event) {
|
|
@@ -144,7 +148,15 @@ export class dragAndDrop extends Plugin {
|
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
150
|
else if (this.getText(event)) {
|
|
147
|
-
|
|
151
|
+
// Dropped markup takes the same sanitizing pass as pasted and
|
|
152
|
+
// assigned content. See GHSA-jhhp-r3r7-v2cg
|
|
153
|
+
const box = this.j.createInside.div();
|
|
154
|
+
box.innerHTML = this.getText(event);
|
|
155
|
+
this.j.e.fire('safeHTML', box);
|
|
156
|
+
fragment =
|
|
157
|
+
box.firstChild !== box.lastChild || !box.firstChild
|
|
158
|
+
? box
|
|
159
|
+
: box.firstChild;
|
|
148
160
|
}
|
|
149
161
|
return fragment;
|
|
150
162
|
}
|
|
@@ -8,5 +8,28 @@
|
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
* @module plugins/file
|
|
10
10
|
*/
|
|
11
|
-
import type { IJodit } from "../../types/index";
|
|
11
|
+
import type { IJodit, IUploaderData } from "../../types/index";
|
|
12
|
+
declare module 'jodit/config' {
|
|
13
|
+
interface Config {
|
|
14
|
+
file: {
|
|
15
|
+
/**
|
|
16
|
+
* Inserts the files uploaded from the file popup's Upload tab. By default
|
|
17
|
+
* each one goes in as a link, an image as well, the way
|
|
18
|
+
* `uploader.defaultHandlerSuccess` inserts a file that isn't an image.
|
|
19
|
+
* ```javascript
|
|
20
|
+
* Jodit.make('#editor', {
|
|
21
|
+
* file: {
|
|
22
|
+
* defaultHandlerSuccess(data) {
|
|
23
|
+
* data.files.forEach(file => {
|
|
24
|
+
* this.s.insertHTML(`<a class="file" href="${data.baseurl + file}">${file}</a>`);
|
|
25
|
+
* });
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
defaultHandlerSuccess?: (this: IJodit, data: IUploaderData) => void;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
12
35
|
export declare function file(editor: IJodit): void;
|
package/esm/plugins/file/file.js
CHANGED
|
@@ -8,6 +8,19 @@ import { pluginSystem } from "../../core/global.js";
|
|
|
8
8
|
import { attr } from "../../core/helpers/utils/attr.js";
|
|
9
9
|
import { Config } from "../../config.js";
|
|
10
10
|
import { FileSelectorWidget } from "../../modules/widget/index.js";
|
|
11
|
+
function insertLinks(data) {
|
|
12
|
+
var _a;
|
|
13
|
+
(_a = data.files) === null || _a === void 0 ? void 0 : _a.forEach(file => {
|
|
14
|
+
const link = this.createInside.element('a', {
|
|
15
|
+
href: data.baseurl + file
|
|
16
|
+
});
|
|
17
|
+
link.textContent = this.o.uploader.getDisplayName.call(this.uploader, data.baseurl, file);
|
|
18
|
+
this.s.insertNode(link);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
Config.prototype.file = {
|
|
22
|
+
defaultHandlerSuccess: insertLinks
|
|
23
|
+
};
|
|
11
24
|
Config.prototype.controls.file = {
|
|
12
25
|
popup: (editor, current, close) => {
|
|
13
26
|
const insert = (url, title = '') => {
|
|
@@ -27,7 +40,10 @@ Config.prototype.controls.file = {
|
|
|
27
40
|
data.files.forEach(file => insert(data.baseurl + file));
|
|
28
41
|
close();
|
|
29
42
|
},
|
|
30
|
-
upload:
|
|
43
|
+
upload: (data) => {
|
|
44
|
+
var _a, _b;
|
|
45
|
+
((_b = (_a = editor.o.file) === null || _a === void 0 ? void 0 : _a.defaultHandlerSuccess) !== null && _b !== void 0 ? _b : insertLinks).call(editor, data);
|
|
46
|
+
},
|
|
31
47
|
url: (url, text) => {
|
|
32
48
|
if (sourceAnchor) {
|
|
33
49
|
attr(sourceAnchor, 'href', url);
|
|
@@ -10,6 +10,26 @@ import { css } from "../../../../core/helpers/utils/css.js";
|
|
|
10
10
|
// Images align with `imageAlignClasses`; the wrappers of iframes and media
|
|
11
11
|
// keep inline styles
|
|
12
12
|
const alignClasses = (editor, elm) => Dom.isTag(elm, 'img') ? editor.o.imageAlignClasses : null;
|
|
13
|
+
// A Normal image sits where the text of its line is aligned (#1531)
|
|
14
|
+
const lineAlign = (editor, elm) => {
|
|
15
|
+
const block = (elm && Dom.closest(elm, Dom.isBlock, editor.editor)) || editor.editor;
|
|
16
|
+
const rtl = css(block, 'direction') === 'rtl';
|
|
17
|
+
switch (css(block, 'text-align')) {
|
|
18
|
+
case 'center':
|
|
19
|
+
case '-webkit-center':
|
|
20
|
+
return 'center';
|
|
21
|
+
case 'justify':
|
|
22
|
+
return 'justify';
|
|
23
|
+
case 'right':
|
|
24
|
+
return 'right';
|
|
25
|
+
case 'end':
|
|
26
|
+
return rtl ? 'left' : 'right';
|
|
27
|
+
case 'start':
|
|
28
|
+
return rtl ? 'right' : 'left';
|
|
29
|
+
default:
|
|
30
|
+
return 'left';
|
|
31
|
+
}
|
|
32
|
+
};
|
|
13
33
|
const currentAlign = (editor, elm) => (elm ? getHAlign(elm, alignClasses(editor, elm)) : 'normal');
|
|
14
34
|
export const align = {
|
|
15
35
|
name: 'left',
|
|
@@ -17,7 +37,8 @@ export const align = {
|
|
|
17
37
|
list: ['Left', 'Right', 'Center', 'Normal'],
|
|
18
38
|
update: (editor, button) => {
|
|
19
39
|
const current = currentAlign(editor, button.target);
|
|
20
|
-
button.state.icon.name =
|
|
40
|
+
button.state.icon.name =
|
|
41
|
+
current === 'normal' ? lineAlign(editor, button.target) : current;
|
|
21
42
|
},
|
|
22
43
|
isActive: (editor, button) => currentAlign(editor, button.target) !== 'normal',
|
|
23
44
|
isChildActive: (editor, button) => {
|
|
@@ -146,7 +146,8 @@ export class AceEditor extends SourceEditor {
|
|
|
146
146
|
}
|
|
147
147
|
return;
|
|
148
148
|
}
|
|
149
|
-
this
|
|
149
|
+
// The source plugin carries the fallback editor's text over when this
|
|
150
|
+
// one is ready; refilling from WYSIWYG here would drop unsynced typing
|
|
150
151
|
tryInitAceEditor();
|
|
151
152
|
};
|
|
152
153
|
editor.e.on('afterSetMode', initAceEditor);
|
|
@@ -21,6 +21,11 @@ export declare class source extends Plugin {
|
|
|
21
21
|
private mirrorContainer;
|
|
22
22
|
private __lock;
|
|
23
23
|
private __oldMirrorValue;
|
|
24
|
+
/**
|
|
25
|
+
* What the source view was last filled with from WYSIWYG, read back from
|
|
26
|
+
* the editor; while the view still holds it, nothing has been typed there
|
|
27
|
+
*/
|
|
28
|
+
private __filledValue;
|
|
24
29
|
private tempMarkerStart;
|
|
25
30
|
private tempMarkerStartReg;
|
|
26
31
|
private tempMarkerEnd;
|
|
@@ -50,6 +55,18 @@ export declare class source extends Plugin {
|
|
|
50
55
|
afterInit(editor: IJodit): void;
|
|
51
56
|
private syncValueFromWYSIWYG;
|
|
52
57
|
private initSourceEditor;
|
|
58
|
+
/**
|
|
59
|
+
* Replaces the source editor (the textarea shown while ACE loads) with
|
|
60
|
+
* the one that just became ready, carrying its text, selection and focus
|
|
61
|
+
* over. Filling the new editor from WYSIWYG instead would drop what was
|
|
62
|
+
* typed since the last, debounced, sync and normalise half-typed markup.
|
|
63
|
+
*/
|
|
64
|
+
private takeOver;
|
|
65
|
+
/**
|
|
66
|
+
* The beautifier arrived after the source view was opened: format the
|
|
67
|
+
* view if nothing has been typed into it, otherwise leave the typing alone
|
|
68
|
+
*/
|
|
69
|
+
private beautifyUntouched;
|
|
53
70
|
/** @override */
|
|
54
71
|
beforeDestruct(): void;
|
|
55
72
|
}
|
|
@@ -37,6 +37,11 @@ export class source extends Plugin {
|
|
|
37
37
|
];
|
|
38
38
|
this.__lock = false;
|
|
39
39
|
this.__oldMirrorValue = '';
|
|
40
|
+
/**
|
|
41
|
+
* What the source view was last filled with from WYSIWYG, read back from
|
|
42
|
+
* the editor; while the view still holds it, nothing has been typed there
|
|
43
|
+
*/
|
|
44
|
+
this.__filledValue = '';
|
|
40
45
|
this.tempMarkerStart = '{start-jodit-selection}';
|
|
41
46
|
this.tempMarkerStartReg = /{start-jodit-selection}/g;
|
|
42
47
|
this.tempMarkerEnd = '{end-jodit-selection}';
|
|
@@ -69,6 +74,7 @@ export class source extends Plugin {
|
|
|
69
74
|
if (new_value !== this.getMirrorValue()) {
|
|
70
75
|
this.setMirrorValue(new_value);
|
|
71
76
|
}
|
|
77
|
+
this.__filledValue = this.getMirrorValue();
|
|
72
78
|
this.__lock = false;
|
|
73
79
|
}
|
|
74
80
|
}
|
|
@@ -271,9 +277,9 @@ export class source extends Plugin {
|
|
|
271
277
|
return;
|
|
272
278
|
}
|
|
273
279
|
loading = true;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
280
|
+
loadNext(editor, editor.o.beautifyHTMLCDNUrlsJS).then(() => {
|
|
281
|
+
addEventListener() && this.beautifyUntouched();
|
|
282
|
+
}, () => null);
|
|
277
283
|
};
|
|
278
284
|
editor.e.on('afterSetMode.source', loadBeautify);
|
|
279
285
|
loadBeautify();
|
|
@@ -294,11 +300,9 @@ export class source extends Plugin {
|
|
|
294
300
|
if (editor.o.sourceEditor !== 'area') {
|
|
295
301
|
const sourceEditor = createSourceEditor(editor.o.sourceEditor, editor, this.mirrorContainer, this.toWYSIWYG, this.fromWYSIWYG);
|
|
296
302
|
sourceEditor.onReadyAlways(() => {
|
|
297
|
-
var _a
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
this.syncValueFromWYSIWYG(true);
|
|
301
|
-
(_b = editor.events) === null || _b === void 0 ? void 0 : _b.fire('sourceEditorReady', editor);
|
|
303
|
+
var _a;
|
|
304
|
+
this.takeOver(sourceEditor);
|
|
305
|
+
(_a = editor.events) === null || _a === void 0 ? void 0 : _a.fire('sourceEditorReady', editor);
|
|
302
306
|
});
|
|
303
307
|
}
|
|
304
308
|
else {
|
|
@@ -309,6 +313,54 @@ export class source extends Plugin {
|
|
|
309
313
|
});
|
|
310
314
|
}
|
|
311
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* Replaces the source editor (the textarea shown while ACE loads) with
|
|
318
|
+
* the one that just became ready, carrying its text, selection and focus
|
|
319
|
+
* over. Filling the new editor from WYSIWYG instead would drop what was
|
|
320
|
+
* typed since the last, debounced, sync and normalise half-typed markup.
|
|
321
|
+
*/
|
|
322
|
+
takeOver(sourceEditor) {
|
|
323
|
+
const previous = this.sourceEditor;
|
|
324
|
+
if (!previous || previous === sourceEditor) {
|
|
325
|
+
this.sourceEditor = sourceEditor;
|
|
326
|
+
this.syncValueFromWYSIWYG(true);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const value = previous.getValue(), start = previous.getSelectionStart(), end = previous.getSelectionEnd(), focused = previous.isFocused, untouched = value === this.__filledValue;
|
|
330
|
+
previous.destruct();
|
|
331
|
+
this.sourceEditor = sourceEditor;
|
|
332
|
+
if (untouched) {
|
|
333
|
+
this.syncValueFromWYSIWYG(true);
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
sourceEditor.setValue(value);
|
|
337
|
+
this.toWYSIWYG();
|
|
338
|
+
}
|
|
339
|
+
if (start >= 0) {
|
|
340
|
+
sourceEditor.setSelectionRange(start, end);
|
|
341
|
+
}
|
|
342
|
+
focused && sourceEditor.focus();
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* The beautifier arrived after the source view was opened: format the
|
|
346
|
+
* view if nothing has been typed into it, otherwise leave the typing alone
|
|
347
|
+
*/
|
|
348
|
+
beautifyUntouched() {
|
|
349
|
+
var _a, _b;
|
|
350
|
+
const value = this.getMirrorValue();
|
|
351
|
+
if (this.j.o.editHTMLDocumentMode ||
|
|
352
|
+
!value ||
|
|
353
|
+
value !== this.__filledValue) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const html = this.j.e.fire('beautifyHTML', value);
|
|
357
|
+
if (isString(html) && html !== value) {
|
|
358
|
+
const focused = (_a = this.sourceEditor) === null || _a === void 0 ? void 0 : _a.isFocused;
|
|
359
|
+
this.setMirrorValue(html);
|
|
360
|
+
this.__filledValue = this.getMirrorValue();
|
|
361
|
+
focused && ((_b = this.sourceEditor) === null || _b === void 0 ? void 0 : _b.focus());
|
|
362
|
+
}
|
|
363
|
+
}
|
|
312
364
|
/** @override */
|
|
313
365
|
beforeDestruct() {
|
|
314
366
|
if (this.sourceEditor) {
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
* Released under MIT see LICENSE.txt in the project root for license information.
|
|
4
4
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* @module plugins/video
|
|
8
|
+
*/
|
|
9
|
+
import type { IJodit, IUploaderData } from "../../types/index";
|
|
6
10
|
declare module 'jodit/config' {
|
|
7
11
|
interface Config {
|
|
8
12
|
video: {
|
|
@@ -29,7 +33,23 @@ declare module 'jodit/config' {
|
|
|
29
33
|
* Default height for video iframe. Default: 345
|
|
30
34
|
*/
|
|
31
35
|
defaultHeight?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Inserts the videos uploaded from the popup's Upload tab, which is shown
|
|
38
|
+
* when `uploader.url` or `uploader.customUploadFunction` is set. By default
|
|
39
|
+
* each one goes in as `<video controls src="…">`.
|
|
40
|
+
* ```javascript
|
|
41
|
+
* Jodit.make('#editor', {
|
|
42
|
+
* video: {
|
|
43
|
+
* defaultHandlerSuccess(data) {
|
|
44
|
+
* data.files.forEach(file => {
|
|
45
|
+
* this.s.insertHTML(`<video controls preload="metadata" src="${data.baseurl + file}"></video>`);
|
|
46
|
+
* });
|
|
47
|
+
* }
|
|
48
|
+
* }
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
defaultHandlerSuccess?: (this: IJodit, data: IUploaderData) => void;
|
|
32
53
|
};
|
|
33
54
|
}
|
|
34
55
|
}
|
|
35
|
-
export {};
|
|
@@ -8,12 +8,22 @@ import { Button } from "../../core/ui/button/index.js";
|
|
|
8
8
|
import { UIBlock, UIForm, UIInput, UITextArea } from "../../core/ui/form/index.js";
|
|
9
9
|
import { Icon } from "../../core/ui/icon.js";
|
|
10
10
|
import { Config } from "../../config.js";
|
|
11
|
-
import { TabsWidget } from "../../modules/widget/index.js";
|
|
11
|
+
import { TabsWidget, UploadTab } from "../../modules/widget/index.js";
|
|
12
12
|
import videoIcon from "./video.svg.js";
|
|
13
|
+
function insertVideos(data) {
|
|
14
|
+
var _a;
|
|
15
|
+
(_a = data.files) === null || _a === void 0 ? void 0 : _a.forEach(file => {
|
|
16
|
+
this.s.insertNode(this.createInside.element('video', {
|
|
17
|
+
controls: '',
|
|
18
|
+
src: data.baseurl + file
|
|
19
|
+
}));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
13
22
|
Config.prototype.video = {
|
|
14
23
|
parseUrlToVideoEmbed: convertMediaUrlToVideoEmbed,
|
|
15
24
|
defaultWidth: 400,
|
|
16
|
-
defaultHeight: 345
|
|
25
|
+
defaultHeight: 345,
|
|
26
|
+
defaultHandlerSuccess: insertVideos
|
|
17
27
|
};
|
|
18
28
|
Icon.set('video', videoIcon);
|
|
19
29
|
Config.prototype.controls.video = {
|
|
@@ -48,6 +58,16 @@ Config.prototype.controls.video = {
|
|
|
48
58
|
close();
|
|
49
59
|
};
|
|
50
60
|
jodit.s.save();
|
|
61
|
+
const { uploader } = jodit.o;
|
|
62
|
+
// Base64 alone would put whole video files into the content
|
|
63
|
+
if (uploader.showTabInFileSelector !== false &&
|
|
64
|
+
(uploader.url || uploader.customUploadFunction)) {
|
|
65
|
+
tabs.push(UploadTab(jodit, data => {
|
|
66
|
+
var _a, _b;
|
|
67
|
+
jodit.s.restore();
|
|
68
|
+
((_b = (_a = jodit.o.video) === null || _a === void 0 ? void 0 : _a.defaultHandlerSuccess) !== null && _b !== void 0 ? _b : insertVideos).call(jodit, data);
|
|
69
|
+
}, { label: 'Drop file', accept: 'video/*' }));
|
|
70
|
+
}
|
|
51
71
|
tabs.push({
|
|
52
72
|
icon: 'link',
|
|
53
73
|
name: 'Link',
|