jodit 4.13.26 → 4.14.0
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 +33 -0
- package/es2015/jodit.css +1 -1
- package/es2015/jodit.fat.min.js +19 -19
- package/es2015/jodit.js +1104 -884
- package/es2015/jodit.min.js +20 -20
- package/es2015/plugins/debug/debug.css +1 -1
- package/es2015/plugins/debug/debug.js +10 -10
- package/es2015/plugins/debug/debug.min.js +4 -4
- package/es2015/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2015/plugins/speech-recognize/speech-recognize.js +2 -2
- package/es2015/plugins/speech-recognize/speech-recognize.min.js +2 -2
- package/es2018/jodit.fat.min.js +19 -19
- package/es2018/jodit.min.js +19 -19
- package/es2018/plugins/debug/debug.min.js +4 -4
- package/es2018/plugins/speech-recognize/speech-recognize.min.js +2 -2
- package/es2021/jodit.css +1 -1
- package/es2021/jodit.fat.min.js +29 -29
- package/es2021/jodit.js +1097 -875
- package/es2021/jodit.min.js +29 -29
- package/es2021/plugins/debug/debug.css +1 -1
- package/es2021/plugins/debug/debug.js +10 -10
- package/es2021/plugins/debug/debug.min.js +4 -4
- package/es2021/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2021/plugins/speech-recognize/speech-recognize.js +2 -2
- package/es2021/plugins/speech-recognize/speech-recognize.min.js +2 -2
- package/es2021.en/jodit.css +1 -1
- package/es2021.en/jodit.fat.min.js +29 -29
- package/es2021.en/jodit.js +1097 -875
- package/es2021.en/jodit.min.js +29 -29
- package/es2021.en/plugins/debug/debug.css +1 -1
- package/es2021.en/plugins/debug/debug.js +10 -10
- package/es2021.en/plugins/debug/debug.min.js +4 -4
- package/es2021.en/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es2021.en/plugins/speech-recognize/speech-recognize.js +2 -2
- package/es2021.en/plugins/speech-recognize/speech-recognize.min.js +2 -2
- package/es5/jodit.css +2 -2
- package/es5/jodit.fat.min.js +2 -2
- package/es5/jodit.js +1221 -925
- 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 +10 -10
- package/es5/plugins/debug/debug.min.js +2 -2
- package/es5/plugins/speech-recognize/speech-recognize.css +1 -1
- package/es5/plugins/speech-recognize/speech-recognize.js +2 -2
- package/es5/plugins/speech-recognize/speech-recognize.min.js +2 -2
- package/es5/polyfills.fat.min.js +1 -1
- package/es5/polyfills.js +1 -1
- package/es5/polyfills.min.js +1 -1
- package/esm/core/constants.js +1 -1
- package/esm/core/create/create.js +2 -2
- package/esm/core/dom/dom.d.ts +21 -13
- package/esm/core/dom/dom.js +58 -87
- package/esm/core/global.js +3 -2
- package/esm/core/helpers/html/apply-styles.js +14 -10
- package/esm/core/helpers/html/clean-from-word.js +8 -4
- package/esm/core/helpers/html/safe-html.js +23 -14
- package/esm/core/helpers/html/strip-tags.js +9 -4
- package/esm/core/helpers/utils/align.d.ts +0 -3
- package/esm/core/helpers/utils/align.js +15 -18
- package/esm/core/helpers/utils/append-script.js +13 -10
- package/esm/core/helpers/utils/attr.d.ts +3 -1
- package/esm/core/helpers/utils/attr.js +18 -0
- package/esm/core/helpers/utils/css.d.ts +18 -1
- package/esm/core/helpers/utils/css.js +15 -0
- package/esm/core/helpers/utils/print.js +17 -14
- package/esm/core/helpers/utils/reset.js +3 -0
- package/esm/core/helpers/utils/selector.js +11 -7
- package/esm/core/selection/helpers/move-node-inside-start.js +1 -1
- package/esm/core/selection/selection.js +29 -26
- package/esm/core/selection/style/api/get-suit-parent.js +10 -0
- package/esm/core/selection/style/api/is-same-attributes.js +5 -2
- package/esm/core/selection/style/api/toggle-attributes.js +8 -9
- package/esm/core/selection/style/api/wrap-unwrapped-text.js +2 -2
- package/esm/core/traits/elms.js +11 -3
- package/esm/core/ui/button/button/button.js +3 -3
- package/esm/core/ui/button/group/group.js +2 -1
- package/esm/core/ui/button/tooltip/tooltip.js +2 -2
- package/esm/core/ui/element.js +1 -1
- package/esm/core/ui/form/inputs/area/area.js +3 -2
- package/esm/core/ui/form/inputs/file/file.js +2 -1
- package/esm/core/ui/form/inputs/input/input.js +5 -5
- package/esm/core/ui/group/group.js +7 -7
- package/esm/core/ui/icon.js +2 -2
- package/esm/core/ui/popup/popup.js +10 -8
- package/esm/core/ui/progress-bar/progress-bar.js +3 -4
- package/esm/core/view/view-with-toolbar.js +1 -1
- package/esm/core/view/view.js +1 -1
- package/esm/jodit.js +30 -28
- package/esm/modules/dialog/alert.js +1 -1
- package/esm/modules/dialog/confirm.js +3 -2
- package/esm/modules/dialog/dialog.d.ts +6 -0
- package/esm/modules/dialog/dialog.js +40 -22
- package/esm/modules/dialog/prompt.js +4 -3
- package/esm/modules/file-browser/builders/context-menu.js +5 -5
- package/esm/modules/file-browser/config.js +3 -1
- package/esm/modules/file-browser/file-browser.js +7 -4
- package/esm/modules/file-browser/listeners/state-listeners.js +6 -6
- package/esm/modules/image-editor/image-editor.js +31 -17
- package/esm/modules/messages/messages.js +2 -1
- package/esm/modules/status-bar/status-bar.js +3 -4
- package/esm/modules/table/table.js +22 -18
- package/esm/modules/toolbar/button/button.js +4 -4
- package/esm/modules/toolbar/button/content.js +12 -4
- package/esm/modules/toolbar/collection/collection.js +4 -2
- package/esm/modules/uploader/config.js +3 -2
- package/esm/modules/uploader/helpers/process-old-browser-drag.js +1 -1
- package/esm/modules/uploader/uploader.js +7 -2
- package/esm/modules/widget/color-picker/color-picker.js +4 -4
- package/esm/modules/widget/file-selector/file-selector.js +6 -6
- package/esm/modules/widget/tabs/tabs.js +5 -5
- package/esm/plugins/add-new-line/add-new-line.js +8 -7
- package/esm/plugins/ai-assistant/ui/ui-ai-assistant.js +4 -4
- package/esm/plugins/backspace/cases/check-join-neighbors.js +4 -1
- package/esm/plugins/clean-html/helpers/visitor/filters/allow-attributes.js +5 -1
- package/esm/plugins/clean-html/helpers/visitor/filters/fill-empty-paragraph.js +1 -1
- package/esm/plugins/color/config.js +4 -4
- package/esm/plugins/copy-format/copy-format.js +1 -1
- package/esm/plugins/delete/delete.js +9 -2
- package/esm/plugins/drag-and-drop/drag-and-drop.js +1 -1
- package/esm/plugins/drag-and-drop-element/drag-and-drop-element.js +1 -1
- package/esm/plugins/enter/helpers/insert-paragraph.js +1 -1
- package/esm/plugins/enter/helpers/process-empty-li-leaf.js +2 -2
- package/esm/plugins/enter/helpers/wrap-text.js +1 -1
- package/esm/plugins/fullsize/fullsize.js +3 -2
- package/esm/plugins/iframe/iframe.js +6 -5
- package/esm/plugins/image-properties/readers/align.js +6 -5
- package/esm/plugins/image-properties/readers/index.js +3 -1
- package/esm/plugins/image-properties/ui/ui-image-form.js +2 -1
- package/esm/plugins/image-properties/writers/index.js +2 -1
- package/esm/plugins/image-properties/writers/link.js +1 -1
- package/esm/plugins/indent/config.js +3 -2
- package/esm/plugins/indent/indent.js +4 -2
- package/esm/plugins/media/media.js +10 -7
- package/esm/plugins/mobile/config.js +2 -2
- package/esm/plugins/paste-storage/paste-storage.js +5 -5
- package/esm/plugins/placeholder/placeholder.js +13 -11
- package/esm/plugins/print/print.js +2 -2
- package/esm/plugins/resize-cells/resize-cells.js +13 -15
- package/esm/plugins/resize-handler/resize-handler.js +3 -4
- package/esm/plugins/resizer/resizer.js +11 -10
- package/esm/plugins/search/helpers/highlight-text-ranges.js +1 -1
- package/esm/plugins/search/ui/search.js +1 -1
- package/esm/plugins/select/select.js +2 -2
- package/esm/plugins/select-cells/select-cells.js +5 -6
- package/esm/plugins/source/editor/engines/ace.js +2 -1
- package/esm/plugins/source/editor/engines/area.js +3 -3
- package/esm/plugins/source/source.js +2 -2
- package/esm/plugins/speech-recognize/speech-recognize.js +1 -1
- package/esm/plugins/sticky/sticky.js +1 -1
- package/esm/plugins/symbols/config.js +3 -2
- package/esm/plugins/symbols/symbols.js +6 -5
- package/esm/plugins/tab/cases/on-tab-inside-li.js +2 -2
- package/esm/plugins/table/config.js +18 -10
- package/esm/plugins/table-keyboard-navigation/table-keyboard-navigation.js +2 -2
- package/esm/plugins/wrap-nodes/wrap-nodes.js +2 -2
- package/esm/plugins/xpath/xpath.js +2 -3
- package/esm/types/index.d.ts +1 -0
- package/esm/types/vdom.d.ts +96 -0
- package/package.json +1 -1
- package/types/core/dom/dom.d.ts +21 -13
- package/types/core/helpers/utils/align.d.ts +0 -3
- package/types/core/helpers/utils/attr.d.ts +3 -1
- package/types/core/helpers/utils/css.d.ts +18 -1
- package/types/modules/dialog/dialog.d.ts +6 -0
- package/types/types/index.d.ts +1 -0
- package/types/types/vdom.d.ts +96 -0
package/esm/core/global.js
CHANGED
|
@@ -7,6 +7,7 @@ import { isJoditObject } from "./helpers/checker/is-jodit-object.js";
|
|
|
7
7
|
import { isString } from "./helpers/checker/is-string.js";
|
|
8
8
|
import { isViewObject } from "./helpers/checker/is-view-object.js";
|
|
9
9
|
import { kebabCase } from "./helpers/string/kebab-case.js";
|
|
10
|
+
import { attr } from "./helpers/utils/attr.js";
|
|
10
11
|
import { css } from "./helpers/utils/css.js";
|
|
11
12
|
import { getClassName } from "./helpers/utils/get-class-name.js";
|
|
12
13
|
import { PluginSystem } from "./plugin/plugin-system.js";
|
|
@@ -76,10 +77,10 @@ export function getContainer(jodit, classFunc, tag = 'div', createInsideEditor =
|
|
|
76
77
|
});
|
|
77
78
|
// Let a strict CSP accept the injected <style>/<script> box
|
|
78
79
|
if (view.o.nonce && (tag === 'style' || tag === 'script')) {
|
|
79
|
-
box
|
|
80
|
+
attr(box, 'nonce', view.o.nonce);
|
|
80
81
|
}
|
|
81
82
|
box.classList.add(`jodit_theme_${view.o.theme || 'default'}`);
|
|
82
|
-
|
|
83
|
+
Dom.append(body, box);
|
|
83
84
|
data[key] = box;
|
|
84
85
|
jodit.hookStatus('beforeDestruct', () => {
|
|
85
86
|
view.events.off(box);
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import { globalDocument, IS_PROD } from "../../constants.js";
|
|
10
10
|
import { Dom } from "../../dom/dom.js";
|
|
11
11
|
import { trim } from "../string/trim.js";
|
|
12
|
+
import { attr } from "../utils/attr.js";
|
|
13
|
+
import { css } from "../utils/css.js";
|
|
12
14
|
import { $$ } from "../utils/selector.js";
|
|
13
15
|
function normalizeCSS(s) {
|
|
14
16
|
return s
|
|
@@ -48,8 +50,8 @@ export function applyStyles(html) {
|
|
|
48
50
|
html = html.substring(0, closeIndex + '</html>'.length);
|
|
49
51
|
}
|
|
50
52
|
const iframe = globalDocument.createElement('iframe');
|
|
51
|
-
iframe
|
|
52
|
-
globalDocument.body
|
|
53
|
+
css(iframe, 'display', 'none');
|
|
54
|
+
Dom.append(globalDocument.body, iframe);
|
|
53
55
|
let convertedString = '', collection = [];
|
|
54
56
|
try {
|
|
55
57
|
const iframeDoc = iframe.contentDocument ||
|
|
@@ -65,7 +67,7 @@ export function applyStyles(html) {
|
|
|
65
67
|
// style normalization strips the `mso-list` hint. See #948
|
|
66
68
|
Dom.each(iframeDoc.body, (node) => {
|
|
67
69
|
if (Dom.isElement(node) &&
|
|
68
|
-
/mso-list:\s*ignore/i.test(node
|
|
70
|
+
/mso-list:\s*ignore/i.test(attr(node, 'style') || '')) {
|
|
69
71
|
Dom.safeRemove(node);
|
|
70
72
|
}
|
|
71
73
|
});
|
|
@@ -77,6 +79,9 @@ export function applyStyles(html) {
|
|
|
77
79
|
continue;
|
|
78
80
|
}
|
|
79
81
|
collection = $$(rules[idx].selectorText, iframeDoc.body);
|
|
82
|
+
// `style.cssText` on purpose: the CSSOM parses the text,
|
|
83
|
+
// drops invalid declarations and re-serializes it. The raw
|
|
84
|
+
// `style` attribute would keep Word-only junk verbatim
|
|
80
85
|
collection.forEach((elm) => {
|
|
81
86
|
elm.style.cssText = normalizeCSS(rules[idx].style.cssText +
|
|
82
87
|
';' +
|
|
@@ -92,14 +97,13 @@ export function applyStyles(html) {
|
|
|
92
97
|
}
|
|
93
98
|
Dom.each(iframeDoc.body, node => {
|
|
94
99
|
if (Dom.isElement(node)) {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
100
|
+
const cssText = attr(node, 'style');
|
|
101
|
+
if (cssText) {
|
|
102
|
+
node.style.cssText =
|
|
103
|
+
normalizeCSS(cssText);
|
|
99
104
|
}
|
|
100
|
-
if (
|
|
101
|
-
|
|
102
|
-
elm.removeAttribute('style');
|
|
105
|
+
if (!attr(node, 'style')) {
|
|
106
|
+
attr(node, 'style', null);
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
});
|
|
@@ -10,6 +10,7 @@ import { globalDocument } from "../../constants.js";
|
|
|
10
10
|
import { Dom } from "../../dom/dom.js";
|
|
11
11
|
import { toArray } from "../array/to-array.js";
|
|
12
12
|
import { trim } from "../string/trim.js";
|
|
13
|
+
import { attr, attrRaw } from "../utils/attr.js";
|
|
13
14
|
/**
|
|
14
15
|
* The method automatically cleans up content from Microsoft Word and other HTML sources to ensure clean, compliant
|
|
15
16
|
* content that matches the look and feel of the site.
|
|
@@ -48,18 +49,21 @@ export function cleanFromWord(html) {
|
|
|
48
49
|
// with `mso-list:Ignore`. They are display-only
|
|
49
50
|
// and must not be imported, otherwise the marker
|
|
50
51
|
// text leaks into the content. See #948
|
|
51
|
-
if (/mso-list:\s*ignore/i.test(node
|
|
52
|
+
if (/mso-list:\s*ignore/i.test(attr(node, 'style') || '')) {
|
|
52
53
|
marks.push(node);
|
|
53
54
|
break;
|
|
54
55
|
}
|
|
55
|
-
toArray(node.attributes).forEach((
|
|
56
|
+
toArray(node.attributes).forEach((attribute) => {
|
|
56
57
|
if ([
|
|
57
58
|
'src',
|
|
58
59
|
'href',
|
|
59
60
|
'rel',
|
|
60
61
|
'content'
|
|
61
|
-
].indexOf(
|
|
62
|
-
|
|
62
|
+
].indexOf(attribute.name.toLowerCase()) === -1) {
|
|
63
|
+
// `attrRaw`: SVG keeps camelCase names
|
|
64
|
+
// (`viewBox`), `attr()` would kebab-case
|
|
65
|
+
// them and silently skip the removal
|
|
66
|
+
attrRaw(node, attribute.name, null);
|
|
63
67
|
}
|
|
64
68
|
});
|
|
65
69
|
}
|
|
@@ -3,11 +3,9 @@
|
|
|
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 helpers/html
|
|
8
|
-
*/
|
|
9
6
|
import { Dom } from "../../dom/dom.js";
|
|
10
7
|
import { attr } from "../utils/index.js";
|
|
8
|
+
import { attrRaw } from "../utils/attr.js";
|
|
11
9
|
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
|
|
12
10
|
/**
|
|
13
11
|
* Integration points where HTML is legitimately allowed inside MathML/SVG foreign content.
|
|
@@ -23,9 +21,10 @@ const HTML_INTEGRATION_POINTS = new Set([
|
|
|
23
21
|
* HTML (e.g. `mglyph` / `style` under `<math>`) that a reparse can hoist into a live node. Legitimate
|
|
24
22
|
* MathML/SVG children and HTML below an integration point are kept.
|
|
25
23
|
*/
|
|
24
|
+
const isMathOrSvg = (node) => Boolean(node && /^(math|svg)$/i.test(node.nodeName));
|
|
26
25
|
function isSmuggledForeignHtml(elm) {
|
|
27
26
|
if (elm.namespaceURI !== HTML_NAMESPACE ||
|
|
28
|
-
|
|
27
|
+
Dom.up(elm, isMathOrSvg) == null) {
|
|
29
28
|
return false;
|
|
30
29
|
}
|
|
31
30
|
for (let parent = elm.parentElement; parent; parent = parent.parentElement) {
|
|
@@ -48,11 +47,17 @@ export function safeHTML(box, options) {
|
|
|
48
47
|
return;
|
|
49
48
|
}
|
|
50
49
|
// Drop HTML smuggled into MathML/SVG before the walk: a reparse would otherwise hoist its hidden
|
|
51
|
-
// markup into a live node.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
// markup into a live node. Collect the candidates first (like the static
|
|
51
|
+
// `querySelectorAll` snapshot did) so removals don't affect the traversal.
|
|
52
|
+
const foreign = [];
|
|
53
|
+
Dom.each(box, node => {
|
|
54
|
+
if (Dom.isElement(node) && Dom.up(node.parentNode, isMathOrSvg)) {
|
|
55
|
+
foreign.push(node);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
for (const elm of foreign) {
|
|
59
|
+
if (Dom.isOrContains(box, elm) && isSmuggledForeignHtml(elm)) {
|
|
60
|
+
Dom.safeRemove(elm);
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
const removeEvents = (_a = options.removeEventAttributes) !== null && _a !== void 0 ? _a : options.removeOnError;
|
|
@@ -70,8 +75,8 @@ export function safeHTML(box, options) {
|
|
|
70
75
|
sanitizeHTMLElement(node, options);
|
|
71
76
|
if (options.safeLinksTarget &&
|
|
72
77
|
node.nodeName === 'A' &&
|
|
73
|
-
node
|
|
74
|
-
const rel = node
|
|
78
|
+
attr(node, 'target') === '_blank') {
|
|
79
|
+
const rel = attr(node, 'rel') || '';
|
|
75
80
|
const parts = rel.split(/\s+/).filter(Boolean);
|
|
76
81
|
if (!parts.includes('noopener')) {
|
|
77
82
|
parts.push('noopener');
|
|
@@ -100,7 +105,9 @@ function removeAllEventAttributes(elm) {
|
|
|
100
105
|
}
|
|
101
106
|
}
|
|
102
107
|
for (const name of toRemove) {
|
|
103
|
-
|
|
108
|
+
// `attrRaw` on purpose: `attr()` kebab-cases the key
|
|
109
|
+
// (`onLoad` → `on-load`), which must not happen in a sanitizer
|
|
110
|
+
attrRaw(elm, name, null);
|
|
104
111
|
effected = true;
|
|
105
112
|
}
|
|
106
113
|
return effected;
|
|
@@ -151,7 +158,7 @@ export function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } =
|
|
|
151
158
|
effected = true;
|
|
152
159
|
}
|
|
153
160
|
const tagName = elm.nodeName.toLowerCase();
|
|
154
|
-
const href = elm
|
|
161
|
+
const href = attr(elm, 'href');
|
|
155
162
|
// Neutralize executable-scheme `href`s with the same normalization used for
|
|
156
163
|
// every other URL attribute (`isDangerousUrl`), which strips control bytes,
|
|
157
164
|
// tabs and newlines and lowercases before matching the scheme. The previous
|
|
@@ -171,8 +178,10 @@ export function sanitizeHTMLElement(elm, { safeJavaScriptLink, removeOnError } =
|
|
|
171
178
|
effected = true;
|
|
172
179
|
}
|
|
173
180
|
// Strip executable schemes from any other URL-bearing attribute.
|
|
181
|
+
// `attrRaw`: the list contains `xlink:href` and the sanitizer must
|
|
182
|
+
// read exactly the attribute it will remove.
|
|
174
183
|
for (const name of URL_ATTRIBUTES) {
|
|
175
|
-
const value = elm
|
|
184
|
+
const value = attrRaw(elm, name);
|
|
176
185
|
if (value && isDangerousUrl(value, tagName)) {
|
|
177
186
|
attr(elm, name, null);
|
|
178
187
|
effected = true;
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import { Dom } from "../../dom/dom.js";
|
|
7
7
|
import { isString } from "../checker/is-string.js";
|
|
8
8
|
import { trim } from "../string/trim.js";
|
|
9
|
-
import { $$ } from "../utils/index.js";
|
|
10
9
|
const NEW_LINE_TAGS = new Set([
|
|
11
10
|
'div',
|
|
12
11
|
'p',
|
|
@@ -30,9 +29,15 @@ export function stripTags(html, doc = document, exclude = null, blockBr = false)
|
|
|
30
29
|
tmp.innerHTML = html;
|
|
31
30
|
}
|
|
32
31
|
else {
|
|
33
|
-
|
|
32
|
+
Dom.append(tmp, html);
|
|
34
33
|
}
|
|
35
|
-
|
|
34
|
+
// Snapshot of all elements (like `querySelectorAll('*')` did) — the loop
|
|
35
|
+
// below removes and unwraps nodes while iterating
|
|
36
|
+
const all = [];
|
|
37
|
+
Dom.each(tmp, node => {
|
|
38
|
+
Dom.isElement(node) && all.push(node);
|
|
39
|
+
});
|
|
40
|
+
all.forEach(p => {
|
|
36
41
|
const pr = p.parentNode;
|
|
37
42
|
if (!pr) {
|
|
38
43
|
return;
|
|
@@ -62,7 +67,7 @@ export function stripTags(html, doc = document, exclude = null, blockBr = false)
|
|
|
62
67
|
// plain text). When `blockBr` is set, separate them with a line
|
|
63
68
|
// break instead, so paragraph structure survives — e.g. the
|
|
64
69
|
// "Insert only Text" paste option. See #1232
|
|
65
|
-
|
|
70
|
+
Dom.before(nx, doc.createTextNode(blockBr ? '%%%jodit-single-br%%%' : ' '));
|
|
66
71
|
}
|
|
67
72
|
});
|
|
68
73
|
return restoreTags(trim(tmp.innerText));
|
|
@@ -15,7 +15,4 @@ export declare function hAlignElement(image: HTMLElement, align: ImageHAlign): v
|
|
|
15
15
|
* Remove text-align style for all selected children
|
|
16
16
|
*/
|
|
17
17
|
export declare function clearAlign(node: Node): void;
|
|
18
|
-
/**
|
|
19
|
-
* Apply align for element
|
|
20
|
-
*/
|
|
21
18
|
export declare function alignElement(command: string, box: HTMLElement): void;
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* Copyright (c) 2013-2026 Valerii Chupurnov. All rights reserved. https://xdsoft.net
|
|
5
5
|
*/
|
|
6
6
|
import { Dom } from "../../dom/index.js";
|
|
7
|
-
import {
|
|
7
|
+
import { attr } from "./attr.js";
|
|
8
|
+
import { clearCenterAlign, css, cssInline } from "./css.js";
|
|
8
9
|
/**
|
|
9
10
|
* Align image
|
|
10
11
|
*/
|
|
@@ -37,10 +38,10 @@ export function hAlignElement(image, align) {
|
|
|
37
38
|
export function clearAlign(node) {
|
|
38
39
|
Dom.each(node, elm => {
|
|
39
40
|
if (Dom.isHTMLElement(elm)) {
|
|
40
|
-
if (elm
|
|
41
|
-
elm
|
|
42
|
-
if (!elm
|
|
43
|
-
elm
|
|
41
|
+
if (cssInline(elm, 'textAlign')) {
|
|
42
|
+
css(elm, 'textAlign', '');
|
|
43
|
+
if (!(attr(elm, 'style') || '').trim().length) {
|
|
44
|
+
attr(elm, 'style', null);
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -49,22 +50,18 @@ export function clearAlign(node) {
|
|
|
49
50
|
/**
|
|
50
51
|
* Apply align for element
|
|
51
52
|
*/
|
|
53
|
+
const ALIGN_BY_COMMAND = new Map([
|
|
54
|
+
['justifyfull', 'justify'],
|
|
55
|
+
['justifyright', 'right'],
|
|
56
|
+
['justifyleft', 'left'],
|
|
57
|
+
['justifycenter', 'center']
|
|
58
|
+
]);
|
|
52
59
|
export function alignElement(command, box) {
|
|
53
60
|
if (Dom.isNode(box) && Dom.isElement(box)) {
|
|
54
61
|
clearAlign(box);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
break;
|
|
59
|
-
case 'justifyright':
|
|
60
|
-
box.style.textAlign = 'right';
|
|
61
|
-
break;
|
|
62
|
-
case 'justifyleft':
|
|
63
|
-
box.style.textAlign = 'left';
|
|
64
|
-
break;
|
|
65
|
-
case 'justifycenter':
|
|
66
|
-
box.style.textAlign = 'center';
|
|
67
|
-
break;
|
|
62
|
+
const align = ALIGN_BY_COMMAND.get(command.toLowerCase());
|
|
63
|
+
if (align) {
|
|
64
|
+
css(box, 'textAlign', align);
|
|
68
65
|
}
|
|
69
66
|
}
|
|
70
67
|
}
|
|
@@ -3,7 +3,9 @@
|
|
|
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
|
+
import { Dom } from "../../dom/dom.js";
|
|
6
7
|
import { isString } from "../checker/is-string.js";
|
|
8
|
+
import { attr } from "./attr.js";
|
|
7
9
|
import { completeUrl } from "./complete-url.js";
|
|
8
10
|
export const alreadyLoadedList = new Map();
|
|
9
11
|
const cacheLoaders = (loader) => {
|
|
@@ -32,10 +34,10 @@ export const appendScriptAsync = cacheLoaders((jodit, url) => {
|
|
|
32
34
|
src: completeUrl(url)
|
|
33
35
|
});
|
|
34
36
|
if (jodit.o.nonce) {
|
|
35
|
-
script
|
|
37
|
+
attr(script, 'nonce', jodit.o.nonce);
|
|
36
38
|
}
|
|
37
39
|
jodit.e.one(script, 'error', reject).one(script, 'load', resolve);
|
|
38
|
-
jodit.od.body
|
|
40
|
+
Dom.append(jodit.od.body, script);
|
|
39
41
|
});
|
|
40
42
|
});
|
|
41
43
|
/**
|
|
@@ -46,22 +48,23 @@ export const appendStyleAsync = cacheLoaders((jodit, url) => {
|
|
|
46
48
|
if (jodit.isInDestruct) {
|
|
47
49
|
return reject();
|
|
48
50
|
}
|
|
49
|
-
const link = jodit.c.element('link'
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
const link = jodit.c.element('link', {
|
|
52
|
+
rel: 'stylesheet',
|
|
53
|
+
media: 'all',
|
|
54
|
+
crossorigin: 'anonymous'
|
|
55
|
+
});
|
|
53
56
|
if (jodit.o.nonce) {
|
|
54
|
-
link
|
|
57
|
+
attr(link, 'nonce', jodit.o.nonce);
|
|
55
58
|
}
|
|
56
59
|
const callback = () => resolve(link);
|
|
57
60
|
!jodit.isInDestruct &&
|
|
58
61
|
jodit.e.on(link, 'load', callback).on(link, 'error', reject);
|
|
59
|
-
link
|
|
62
|
+
attr(link, 'href', completeUrl(url));
|
|
60
63
|
if (jodit.o.shadowRoot) {
|
|
61
|
-
jodit.o.shadowRoot
|
|
64
|
+
Dom.append(jodit.o.shadowRoot, link);
|
|
62
65
|
}
|
|
63
66
|
else {
|
|
64
|
-
jodit.od.body
|
|
67
|
+
Dom.append(jodit.od.body, link);
|
|
65
68
|
}
|
|
66
69
|
});
|
|
67
70
|
});
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* @module helpers/utils
|
|
8
8
|
*/
|
|
9
|
-
import type { IDictionary } from "../../../types/index";
|
|
9
|
+
import type { IDictionary, VElement } from "../../../types/index";
|
|
10
10
|
/**
|
|
11
11
|
* Get attribute
|
|
12
12
|
*/
|
|
@@ -23,3 +23,5 @@ export declare function attr(elm: Element, key: string, value: string | number |
|
|
|
23
23
|
* Set or remove several attributes
|
|
24
24
|
*/
|
|
25
25
|
export declare function attr(elm: Element, attributes: IDictionary<string | number | boolean | null>): null;
|
|
26
|
+
export declare function attrRaw(elm: VElement, name: string): null | string;
|
|
27
|
+
export declare function attrRaw(elm: VElement, name: string, value: string | null): void;
|
|
@@ -56,3 +56,21 @@ export function attr(elm, keyOrAttributes, value) {
|
|
|
56
56
|
}
|
|
57
57
|
return elm.getAttribute(key);
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Exact-name attribute access: no camelCase → kebab-case conversion, no
|
|
61
|
+
* `data-` fallback and no `px` stripping. Use it where the attribute name must
|
|
62
|
+
* be taken verbatim — sanitizers and attribute comparison (`onLoad`, `viewBox`,
|
|
63
|
+
* `xlink:href`). `null` removes the attribute.
|
|
64
|
+
*/
|
|
65
|
+
export function attrRaw(elm, name, value) {
|
|
66
|
+
if (value === undefined) {
|
|
67
|
+
return elm.getAttribute(name);
|
|
68
|
+
}
|
|
69
|
+
if (value == null) {
|
|
70
|
+
if (elm.hasAttribute(name)) {
|
|
71
|
+
elm.removeAttribute(name);
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
elm.setAttribute(name, value);
|
|
76
|
+
}
|
|
@@ -6,13 +6,30 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* @module helpers/utils
|
|
8
8
|
*/
|
|
9
|
-
import type { IStyle, StyleValue } from "../../../types/index";
|
|
9
|
+
import type { IStyle, StyleValue, VHTMLElement } from "../../../types/index";
|
|
10
10
|
export declare function css(element: HTMLElement, key: 'left' | 'top' | 'bottom' | 'right' | 'width' | 'min' | 'max' | 'height' | 'margin' | 'padding' | 'fontsize' | 'font-size'): number;
|
|
11
11
|
export declare function css(element: HTMLElement, key: keyof CSSStyleDeclaration): string | number;
|
|
12
12
|
export declare function css(element: HTMLElement, key: string | IStyle): string | number;
|
|
13
13
|
export declare function css(element: HTMLElement, key: string | IStyle, value: StyleValue): string | number;
|
|
14
|
+
/**
|
|
15
|
+
* @deprecated Use `cssInline(element, key)` for an exact inline read.
|
|
16
|
+
* The boolean mode still normalizes the value (`'0px'` → `0`, `bold` → `700`).
|
|
17
|
+
*/
|
|
14
18
|
export declare function css(element: HTMLElement, key: string | IStyle, onlyStyleMode: boolean): string | number;
|
|
15
19
|
/**
|
|
16
20
|
* Clear center align
|
|
17
21
|
*/
|
|
22
|
+
/**
|
|
23
|
+
* Read the exact inline value of a CSS property (the `style` attribute).
|
|
24
|
+
* Unlike `css()` it never touches computed styles (no layout access) and does
|
|
25
|
+
* not normalize the result, so it is safe for exact string comparisons and
|
|
26
|
+
* save/restore. Returns an empty string when the property is not set inline.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```js
|
|
30
|
+
* cssInline(elm, 'zIndex'); // '' or '100'
|
|
31
|
+
* cssInline(elm, 'width'); // '0px' (css(elm, 'width', true) would return 0)
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function cssInline(element: VHTMLElement, key: string): string;
|
|
18
35
|
export declare const clearCenterAlign: (image: HTMLElement) => void;
|
|
@@ -62,6 +62,21 @@ export function css(element, key, value, onlyStyleMode = false) {
|
|
|
62
62
|
/**
|
|
63
63
|
* Clear center align
|
|
64
64
|
*/
|
|
65
|
+
/**
|
|
66
|
+
* Read the exact inline value of a CSS property (the `style` attribute).
|
|
67
|
+
* Unlike `css()` it never touches computed styles (no layout access) and does
|
|
68
|
+
* not normalize the result, so it is safe for exact string comparisons and
|
|
69
|
+
* save/restore. Returns an empty string when the property is not set inline.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```js
|
|
73
|
+
* cssInline(elm, 'zIndex'); // '' or '100'
|
|
74
|
+
* cssInline(elm, 'width'); // '0px' (css(elm, 'width', true) would return 0)
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function cssInline(element, key) {
|
|
78
|
+
return element.style.getPropertyValue(kebabCase(key));
|
|
79
|
+
}
|
|
65
80
|
export const clearCenterAlign = (image) => {
|
|
66
81
|
if (css(image, 'display') === 'block') {
|
|
67
82
|
css(image, 'display', '');
|
|
@@ -7,32 +7,35 @@ import { Dom } from "../../dom/dom.js";
|
|
|
7
7
|
import { isString } from "../checker/is-string.js";
|
|
8
8
|
import { attr } from "./attr.js";
|
|
9
9
|
import { css } from "./css.js";
|
|
10
|
-
import { $$ } from "./selector.js";
|
|
11
10
|
/**
|
|
12
11
|
* Fixes image sizes and sets absolute paths to images
|
|
13
12
|
*/
|
|
14
13
|
function fixedAssetsSizeAndAbsoluteLinks(editor, points) {
|
|
15
14
|
const restoreAttributes = [];
|
|
15
|
+
const images = [];
|
|
16
|
+
Dom.each(editor.editor, node => {
|
|
17
|
+
Dom.isTag(node, 'img') && images.push(node);
|
|
18
|
+
});
|
|
16
19
|
try {
|
|
17
|
-
|
|
20
|
+
images.forEach(item => {
|
|
18
21
|
const previousAttrs = [
|
|
19
22
|
attr(item, 'width'),
|
|
20
23
|
attr(item, 'height'),
|
|
21
|
-
item
|
|
24
|
+
attr(item, 'src')
|
|
22
25
|
];
|
|
23
26
|
attr(item, {
|
|
24
27
|
width: item.offsetWidth + points,
|
|
25
28
|
height: item.offsetHeight + points
|
|
26
29
|
});
|
|
27
30
|
const a = editor.createInside.a();
|
|
28
|
-
editor.ed.body
|
|
29
|
-
a
|
|
30
|
-
|
|
31
|
+
Dom.append(editor.ed.body, a);
|
|
32
|
+
attr(a, 'href', attr(item, 'src') || '');
|
|
33
|
+
// reading the `href` property resolves the URL to an absolute one
|
|
34
|
+
attr(item, 'src', a.href);
|
|
31
35
|
Dom.safeRemove(a);
|
|
32
36
|
restoreAttributes.push(() => {
|
|
33
|
-
var _a;
|
|
34
|
-
item.src = (_a = previousAttrs[2]) !== null && _a !== void 0 ? _a : '';
|
|
35
37
|
attr(item, {
|
|
38
|
+
src: previousAttrs[2],
|
|
36
39
|
width: previousAttrs[0] || null,
|
|
37
40
|
height: previousAttrs[1] || null
|
|
38
41
|
});
|
|
@@ -62,7 +65,7 @@ export function previewBox(editor, defaultValue, points = 'px', container = null
|
|
|
62
65
|
}
|
|
63
66
|
let div = editor.c.div('jodit__preview-box jodit-context');
|
|
64
67
|
if (container) {
|
|
65
|
-
|
|
68
|
+
Dom.append(container, div);
|
|
66
69
|
}
|
|
67
70
|
css(div, {
|
|
68
71
|
position: 'relative',
|
|
@@ -77,7 +80,7 @@ export function previewBox(editor, defaultValue, points = 'px', container = null
|
|
|
77
80
|
minHeight: 600,
|
|
78
81
|
border: 0
|
|
79
82
|
});
|
|
80
|
-
|
|
83
|
+
Dom.append(div, iframe);
|
|
81
84
|
const myWindow = iframe.contentWindow;
|
|
82
85
|
if (myWindow) {
|
|
83
86
|
editor.e.fire('generateDocumentStructure.iframe', myWindow.document, editor);
|
|
@@ -87,7 +90,7 @@ export function previewBox(editor, defaultValue, points = 'px', container = null
|
|
|
87
90
|
const elm = myWindow.document.body;
|
|
88
91
|
const resizeObserver = new ResizeObserver(editor.async.debounce(() => {
|
|
89
92
|
resizeObserver.unobserve(elm);
|
|
90
|
-
iframe
|
|
93
|
+
css(iframe, 'height', elm.offsetHeight + 20);
|
|
91
94
|
editor.async.requestAnimationFrame(() => {
|
|
92
95
|
!destructed && resizeObserver.observe(elm);
|
|
93
96
|
});
|
|
@@ -120,7 +123,7 @@ export function previewBox(editor, defaultValue, points = 'px', container = null
|
|
|
120
123
|
if (Dom.isElement(c)) {
|
|
121
124
|
const newNode = box.ownerDocument.createElement(c.nodeName);
|
|
122
125
|
for (let j = 0; j < c.attributes.length; j += 1) {
|
|
123
|
-
attr(newNode, c.attributes[j].
|
|
126
|
+
attr(newNode, c.attributes[j].name, c.attributes[j].value);
|
|
124
127
|
}
|
|
125
128
|
if (c.childNodes.length === 0 || Dom.isTag(c, 'table')) {
|
|
126
129
|
switch (c.nodeName) {
|
|
@@ -140,13 +143,13 @@ export function previewBox(editor, defaultValue, points = 'px', container = null
|
|
|
140
143
|
setHTML(newNode, c);
|
|
141
144
|
}
|
|
142
145
|
try {
|
|
143
|
-
|
|
146
|
+
Dom.append(box, newNode);
|
|
144
147
|
}
|
|
145
148
|
catch (_a) { }
|
|
146
149
|
}
|
|
147
150
|
else {
|
|
148
151
|
try {
|
|
149
|
-
|
|
152
|
+
Dom.append(box, c.cloneNode(true));
|
|
150
153
|
}
|
|
151
154
|
catch (_b) { }
|
|
152
155
|
}
|
|
@@ -18,6 +18,9 @@ const map = {};
|
|
|
18
18
|
export function reset(key) {
|
|
19
19
|
var _a, _b;
|
|
20
20
|
if (!(key in map)) {
|
|
21
|
+
// Raw DOM API on purpose: this bootstrap helper is imported by
|
|
22
|
+
// `to-array`, which the `Dom` module itself depends on — importing
|
|
23
|
+
// `Dom` here would create a circular dependency
|
|
21
24
|
const iframe = globalDocument.createElement('iframe');
|
|
22
25
|
try {
|
|
23
26
|
iframe.src = 'about:blank';
|
|
@@ -66,14 +66,18 @@ export const refs = (root) => {
|
|
|
66
66
|
if ('container' in root) {
|
|
67
67
|
root = root.container;
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
const def = {};
|
|
70
|
+
Dom.each(root, child => {
|
|
71
|
+
if (Dom.isElement(child) &&
|
|
72
|
+
(child.hasAttribute('ref') || child.hasAttribute('data-ref'))) {
|
|
73
|
+
const key = attr(child, '-ref');
|
|
74
|
+
if (key && isString(key)) {
|
|
75
|
+
def[camelCase(key)] = child;
|
|
76
|
+
def[key] = child;
|
|
77
|
+
}
|
|
74
78
|
}
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
});
|
|
80
|
+
return def;
|
|
77
81
|
};
|
|
78
82
|
/**
|
|
79
83
|
* Calculate full CSS selector
|
|
@@ -17,7 +17,7 @@ export function moveNodeInsideStart(j, node, start) {
|
|
|
17
17
|
Dom.isContentEditable(sibling, j.editor) &&
|
|
18
18
|
(!anotherSibling || !Dom.closest(node, Dom.isElement, j.editor))) {
|
|
19
19
|
if (start || !sibling.firstChild) {
|
|
20
|
-
|
|
20
|
+
Dom.append(sibling, node);
|
|
21
21
|
}
|
|
22
22
|
else {
|
|
23
23
|
Dom.before(sibling.firstChild, node);
|