@webitel/ui-sdk 26.8.118 → 26.8.120
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/{clients-DWcx-Mfk.js → clients-CwGTZ_pb.js} +1502 -170
- package/dist/{components-CeOlovz8.js → components-Bl2fDyms.js} +933 -948
- package/dist/ui-sdk.css +1 -1
- package/dist/ui-sdk.js +1 -1
- package/dist/ui-sdk.umd.cjs +2158 -2119
- package/dist/{wt-chat-emoji-v9gpIuHe.js → wt-chat-emoji-CQ-ZUYcm.js} +1 -1
- package/dist/{wt-display-chip-items-Bob7miL_.js → wt-display-chip-items-yuaxgXrU.js} +1 -1
- package/dist/wt-label-CiLpqe61.js +34 -0
- package/dist/wt-rich-text-editor-DyGnGabB.js +130 -0
- package/dist/{wt-send-message-popup-9O69XY0L.js → wt-send-message-popup-Ig3Uq6So.js} +2 -2
- package/dist/{wt-type-extension-value-input-55YmFcBp.js → wt-type-extension-value-input-B4fjVEic.js} +2 -2
- package/dist/{wt-vidstack-player-ETPSioGH.js → wt-vidstack-player-DitE8XQx.js} +1 -1
- package/package.json +3 -1
- package/src/components/index.js +14 -0
- package/src/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.compat.spec.js +127 -0
- package/src/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.spec.js +157 -0
- package/src/components/on-demand/wt-rich-text-editor/__tests__/loadTinyMce.spec.js +54 -0
- package/src/components/on-demand/wt-rich-text-editor/_internals/loadTinyMce.ts +31 -0
- package/src/components/on-demand/wt-rich-text-editor/_internals/wt-rich-text-editor-skeleton.vue +54 -0
- package/src/components/on-demand/wt-rich-text-editor/wt-rich-text-editor.vue +223 -0
- package/types/.tsbuildinfo +1 -1
- package/types/components/index.d.ts +7 -0
- package/types/components/on-demand/wt-call-media-action/wt-call-media-action.vue.d.ts +2 -2
- package/types/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.compat.spec.d.ts +1 -0
- package/types/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.spec.d.ts +1 -0
- package/types/components/on-demand/wt-rich-text-editor/__tests__/loadTinyMce.spec.d.ts +1 -0
- package/types/components/on-demand/wt-rich-text-editor/_internals/loadTinyMce.d.ts +1 -0
- package/types/components/on-demand/wt-rich-text-editor/_internals/wt-rich-text-editor-skeleton.vue.d.ts +3 -0
- package/types/components/on-demand/wt-rich-text-editor/wt-rich-text-editor.vue.d.ts +65 -0
- package/types/components/transitions/cases/wt-replace-transition.vue.d.ts +1 -1
- package/types/components/wt-datepicker/wt-datepicker.vue.d.ts +2 -2
- package/types/components/wt-input-text/wt-input-text.vue.d.ts +1 -1
- package/types/components/wt-player/wt-player.vue.d.ts +1 -1
- package/types/components/wt-search-bar/wt-search-bar.vue.d.ts +2 -2
- package/types/components/wt-table/wt-table.vue.d.ts +1 -1
- package/types/components/wt-textarea/wt-textarea.vue.d.ts +1 -1
- package/types/components/wt-time-input/wt-time-input.vue.d.ts +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as e, i as t } from "./components-
|
|
1
|
+
import { C as e, i as t } from "./components-Bl2fDyms.js";
|
|
2
2
|
import { t as n } from "./_plugin-vue_export-helper-B3ysoDQm.js";
|
|
3
3
|
import { t as r } from "./utils-DHSDTqJG.js";
|
|
4
4
|
import { Fragment as i, computed as a, createCommentVNode as o, createElementBlock as s, createTextVNode as c, createVNode as l, defineComponent as u, openBlock as d, renderList as f, renderSlot as p, toDisplayString as m, unref as h, withCtx as g } from "vue";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { t as e } from "./_plugin-vue_export-helper-B3ysoDQm.js";
|
|
2
|
+
import { createBlock as t, createCommentVNode as n, createElementBlock as r, createStaticVNode as i, createTextVNode as a, defineComponent as o, normalizeClass as s, openBlock as c, renderSlot as l, resolveComponent as u, toDisplayString as d, withCtx as f } from "vue";
|
|
3
|
+
//#region src/components/on-demand/wt-rich-text-editor/_internals/wt-rich-text-editor-skeleton.vue
|
|
4
|
+
var p = {}, m = {
|
|
5
|
+
"aria-busy": "true",
|
|
6
|
+
class: "wt-rich-text-editor-skeleton"
|
|
7
|
+
};
|
|
8
|
+
function h(e, t) {
|
|
9
|
+
return c(), r("div", m, [...t[0] ||= [i("<div class=\"wt-rich-text-editor-skeleton__toolbar\" data-v-0802d94d></div><div class=\"wt-rich-text-editor-skeleton__body\" data-v-0802d94d><div class=\"wt-rich-text-editor-skeleton__line\" data-v-0802d94d></div><div class=\"wt-rich-text-editor-skeleton__line\" data-v-0802d94d></div><div class=\"wt-rich-text-editor-skeleton__line wt-rich-text-editor-skeleton__line--short\" data-v-0802d94d></div></div>", 2)]]);
|
|
10
|
+
}
|
|
11
|
+
var g = /*#__PURE__*/ e(p, [["render", h], ["__scopeId", "data-v-0802d94d"]]), _ = /*#__PURE__*/ e(/* @__PURE__ */ o({
|
|
12
|
+
__name: "wt-label",
|
|
13
|
+
props: {
|
|
14
|
+
disabled: { type: Boolean },
|
|
15
|
+
invalid: { type: Boolean },
|
|
16
|
+
required: { type: Boolean },
|
|
17
|
+
hint: {}
|
|
18
|
+
},
|
|
19
|
+
setup(e) {
|
|
20
|
+
return (i, o) => {
|
|
21
|
+
let p = u("wt-hint");
|
|
22
|
+
return c(), r("label", { class: s([{
|
|
23
|
+
"wt-label--invalid": e.invalid,
|
|
24
|
+
"wt-label--disabled": e.disabled,
|
|
25
|
+
"wt-label--required": e.required
|
|
26
|
+
}, "wt-label typo-subtitle-2"]) }, [l(i.$slots, "default", {}, void 0, !0), e.hint ? (c(), t(p, { key: 0 }, {
|
|
27
|
+
default: f(() => [a(d(e.hint), 1)], void 0, !0),
|
|
28
|
+
_: 1
|
|
29
|
+
})) : n("", !0)], 2);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}), [["__scopeId", "data-v-7d80b998"]]);
|
|
33
|
+
//#endregion
|
|
34
|
+
export { g as n, _ as t };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { n as e, t } from "./wt-label-CiLpqe61.js";
|
|
2
|
+
import { t as n } from "./_plugin-vue_export-helper-B3ysoDQm.js";
|
|
3
|
+
import { computed as r, createBlock as i, createCommentVNode as a, createElementBlock as o, createTextVNode as s, defineComponent as c, guardReactiveProps as l, mergeModels as u, mergeProps as d, normalizeProps as f, onBeforeUnmount as p, openBlock as m, ref as h, renderSlot as g, toDisplayString as _, unref as v, useId as y, useModel as b, withCtx as x } from "vue";
|
|
4
|
+
import S from "@tinymce/tinymce-vue";
|
|
5
|
+
//#region src/components/on-demand/wt-rich-text-editor/_internals/loadTinyMce.ts
|
|
6
|
+
var C = null;
|
|
7
|
+
function w() {
|
|
8
|
+
return C ??= import("tinymce/tinymce").then(() => Promise.all([
|
|
9
|
+
import("tinymce/icons/default"),
|
|
10
|
+
import("tinymce/models/dom"),
|
|
11
|
+
import("tinymce/themes/silver"),
|
|
12
|
+
import("tinymce/plugins/advlist"),
|
|
13
|
+
import("tinymce/plugins/emoticons"),
|
|
14
|
+
import("tinymce/plugins/emoticons/js/emojis"),
|
|
15
|
+
import("tinymce/plugins/fullscreen"),
|
|
16
|
+
import("tinymce/plugins/image"),
|
|
17
|
+
import("tinymce/plugins/link"),
|
|
18
|
+
import("tinymce/plugins/lists"),
|
|
19
|
+
import("tinymce/plugins/table"),
|
|
20
|
+
import("tinymce/skins/ui/oxide/skin.css")
|
|
21
|
+
]).then(() => void 0)), C;
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/components/on-demand/wt-rich-text-editor/wt-rich-text-editor.vue?vue&type=script&setup=true&lang.ts
|
|
25
|
+
var T = { class: "wt-rich-text-editor" }, E = /*#__PURE__*/ n(/* @__PURE__ */ c({
|
|
26
|
+
compatConfig: { MODE: 3 },
|
|
27
|
+
__name: "wt-rich-text-editor",
|
|
28
|
+
props: /*@__PURE__*/ u({
|
|
29
|
+
label: {},
|
|
30
|
+
labelProps: {},
|
|
31
|
+
output: { default: "html" },
|
|
32
|
+
height: { default: 300 },
|
|
33
|
+
disabled: { type: Boolean },
|
|
34
|
+
placeholder: {},
|
|
35
|
+
toolbar: {},
|
|
36
|
+
plugins: {}
|
|
37
|
+
}, {
|
|
38
|
+
modelValue: {},
|
|
39
|
+
modelModifiers: {}
|
|
40
|
+
}),
|
|
41
|
+
emits: ["update:modelValue"],
|
|
42
|
+
setup(n) {
|
|
43
|
+
let c = {
|
|
44
|
+
...S,
|
|
45
|
+
name: "TinyMceEditor",
|
|
46
|
+
compatConfig: { MODE: 3 }
|
|
47
|
+
}, u = n, C = b(n, "modelValue"), E = y(), D = h(!1);
|
|
48
|
+
w().then(() => {
|
|
49
|
+
D.value = !0;
|
|
50
|
+
});
|
|
51
|
+
let O = [
|
|
52
|
+
"advlist",
|
|
53
|
+
"emoticons",
|
|
54
|
+
"fullscreen",
|
|
55
|
+
"image",
|
|
56
|
+
"link",
|
|
57
|
+
"lists",
|
|
58
|
+
"table"
|
|
59
|
+
], k = [
|
|
60
|
+
"undo redo",
|
|
61
|
+
"bold italic underline strikethrough",
|
|
62
|
+
"fullscreen",
|
|
63
|
+
"fontfamily fontsize",
|
|
64
|
+
"alignleft aligncenter alignright alignjustify",
|
|
65
|
+
"numlist bullist",
|
|
66
|
+
"forecolor backcolor removeformat",
|
|
67
|
+
"emoticons image link table"
|
|
68
|
+
].join(" | "), A = r(() => u.output === "html"), j = r(() => u.plugins ?? (A.value ? O : [])), M = r(() => u.toolbar ?? (A.value ? k : "undo redo"));
|
|
69
|
+
function N() {
|
|
70
|
+
let e = getComputedStyle(document.documentElement);
|
|
71
|
+
return {
|
|
72
|
+
backgroundColor: e.getPropertyValue("--content-wrapper-color").trim(),
|
|
73
|
+
color: e.getPropertyValue("--wt-text-field-text-color").trim()
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
let P = r(() => {
|
|
77
|
+
let { backgroundColor: e, color: t } = N();
|
|
78
|
+
return {
|
|
79
|
+
height: Number(u.height),
|
|
80
|
+
menubar: !1,
|
|
81
|
+
statusbar: A.value,
|
|
82
|
+
toolbar_mode: "sliding",
|
|
83
|
+
toolbar_sticky: !0,
|
|
84
|
+
placeholder: u.placeholder,
|
|
85
|
+
skin: !1,
|
|
86
|
+
content_css: !1,
|
|
87
|
+
content_style: `body { background-color: ${e}; color: ${t}; }`
|
|
88
|
+
};
|
|
89
|
+
}), F = null, I = new MutationObserver(() => {
|
|
90
|
+
F?.getBody() && F.dom.setStyles(F.getBody(), N());
|
|
91
|
+
});
|
|
92
|
+
function L(e, t) {
|
|
93
|
+
F = t, I.observe(document.documentElement, {
|
|
94
|
+
attributes: !0,
|
|
95
|
+
attributeFilter: ["class"]
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return p(() => {
|
|
99
|
+
I.disconnect(), F = null;
|
|
100
|
+
}), (r, u) => (m(), o("div", T, [n.label || r.$slots.label ? (m(), i(t, d({ key: 0 }, n.labelProps, {
|
|
101
|
+
disabled: n.disabled,
|
|
102
|
+
for: v(E)
|
|
103
|
+
}), {
|
|
104
|
+
default: x(() => [g(r.$slots, "label", f(l({ label: n.label })), () => [s(_(n.label), 1)], !0)], void 0, !0),
|
|
105
|
+
_: 3
|
|
106
|
+
}, 16, ["disabled", "for"])) : a("", !0), D.value ? (m(), i(c, {
|
|
107
|
+
key: 1,
|
|
108
|
+
id: v(E),
|
|
109
|
+
modelValue: C.value,
|
|
110
|
+
"onUpdate:modelValue": u[0] ||= (e) => C.value = e,
|
|
111
|
+
disabled: n.disabled,
|
|
112
|
+
init: P.value,
|
|
113
|
+
"output-format": n.output,
|
|
114
|
+
plugins: j.value,
|
|
115
|
+
toolbar: M.value,
|
|
116
|
+
"license-key": "gpl",
|
|
117
|
+
onInit: L
|
|
118
|
+
}, null, 8, [
|
|
119
|
+
"id",
|
|
120
|
+
"modelValue",
|
|
121
|
+
"disabled",
|
|
122
|
+
"init",
|
|
123
|
+
"output-format",
|
|
124
|
+
"plugins",
|
|
125
|
+
"toolbar"
|
|
126
|
+
])) : (m(), i(e, { key: 2 }))]));
|
|
127
|
+
}
|
|
128
|
+
}), [["__scopeId", "data-v-47d8ed9a"]]);
|
|
129
|
+
//#endregion
|
|
130
|
+
export { E as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { n as e } from "./components-
|
|
1
|
+
import { n as e } from "./components-Bl2fDyms.js";
|
|
2
2
|
import "./convertDuration-CW5qzlwH.js";
|
|
3
|
-
import { i as t, r as n } from "./clients-
|
|
3
|
+
import { i as t, r as n } from "./clients-CwGTZ_pb.js";
|
|
4
4
|
import { n as r } from "./vue-i18n-VBiROMZ4.js";
|
|
5
5
|
import { n as i } from "./useEventBus-B-ZeklLu.js";
|
|
6
6
|
import { createBlock as a, createTextVNode as o, createVNode as s, defineComponent as c, mergeProps as l, onMounted as u, openBlock as d, ref as f, resolveComponent as p, toDisplayString as m, unref as h, withCtx as g } from "vue";
|
package/dist/{wt-type-extension-value-input-55YmFcBp.js → wt-type-extension-value-input-B4fjVEic.js}
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { a as e, c as t, i as n, n as r, r as i, s as a, t as o } from "./dist-EXdgfpvN.js";
|
|
2
2
|
import { n as s } from "./genClient-CvWlQ0TZ.js";
|
|
3
3
|
import { a as c } from "./caseConverters-DGri4uJo.js";
|
|
4
|
-
import { a as l, o as u, r as d, s as f } from "./components-
|
|
5
|
-
import { a as p, n as m, t as h } from "./clients-
|
|
4
|
+
import { a as l, o as u, r as d, s as f } from "./components-Bl2fDyms.js";
|
|
5
|
+
import { a as p, n as m, t as h } from "./clients-CwGTZ_pb.js";
|
|
6
6
|
import { a as g, b as _, d as v, f as y, g as b, i as x, n as S, o as C, r as w, s as T, t as E, y as D } from "./enums-CsINFPBu.js";
|
|
7
7
|
import { n as ee, t as te } from "./vue-i18n-VBiROMZ4.js";
|
|
8
8
|
import "./wt-toast-pXV44Y3v.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webitel/ui-sdk",
|
|
3
|
-
"version": "26.8.
|
|
3
|
+
"version": "26.8.120",
|
|
4
4
|
"private": false,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "npm run docs:dev",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"@morev/vue-transitions": "^3.0.5",
|
|
63
63
|
"@primeuix/themes": "^1.1.1",
|
|
64
64
|
"@tailwindcss/vite": "^4.1.7",
|
|
65
|
+
"@tinymce/tinymce-vue": "^6.3.0",
|
|
65
66
|
"@vuepic/vue-datepicker": "^4.5.1",
|
|
66
67
|
"@vueuse/components": "^14.0.0",
|
|
67
68
|
"autosize": "^6.0.1",
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
"qs-esm": "^7.0.3",
|
|
80
81
|
"sortablejs": "^1.15.3",
|
|
81
82
|
"tailwindcss": "^4.1.7",
|
|
83
|
+
"tinymce": "^8.9.2",
|
|
82
84
|
"vue-multiselect": "^3.4.0",
|
|
83
85
|
"vue-observe-visibility": "^2.0.0-alpha.1",
|
|
84
86
|
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
|
package/src/components/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineAsyncComponent } from 'vue';
|
|
2
2
|
|
|
3
|
+
import WtRichTextEditorSkeleton from './on-demand/wt-rich-text-editor/_internals/wt-rich-text-editor-skeleton.vue';
|
|
3
4
|
import WtReplaceTransition from './transitions/cases/wt-replace-transition.vue';
|
|
4
5
|
import WtAppHeader from './wt-app-header/wt-app-header.vue';
|
|
5
6
|
import WtAppNavigator from './wt-app-header/wt-app-navigator.vue';
|
|
@@ -181,6 +182,17 @@ const WtNavigationMenu = defineAsyncComponent(
|
|
|
181
182
|
() =>
|
|
182
183
|
import('./on-demand/wt-navigation-menu/components/wt-navigation-menu.vue'),
|
|
183
184
|
);
|
|
185
|
+
/**
|
|
186
|
+
* TinyMCE lives in this chunk alone: an app that never renders an editor never
|
|
187
|
+
* downloads it. The skeleton covers the download.
|
|
188
|
+
* @type {typeof import('./on-demand/wt-rich-text-editor/wt-rich-text-editor.vue').default}
|
|
189
|
+
*/
|
|
190
|
+
const WtRichTextEditor = defineAsyncComponent({
|
|
191
|
+
loader: () =>
|
|
192
|
+
import('./on-demand/wt-rich-text-editor/wt-rich-text-editor.vue'),
|
|
193
|
+
loadingComponent: WtRichTextEditorSkeleton,
|
|
194
|
+
delay: 0,
|
|
195
|
+
});
|
|
184
196
|
/** @type {typeof import('./on-demand/wt-selection-popup/wt-selection-popup.vue').default} */
|
|
185
197
|
const WtSelectionPopup = defineAsyncComponent(
|
|
186
198
|
() => import('./on-demand/wt-selection-popup/wt-selection-popup.vue'),
|
|
@@ -233,6 +245,7 @@ const Components = {
|
|
|
233
245
|
WtLoader,
|
|
234
246
|
WtRoundedAction,
|
|
235
247
|
WtReplaceTransition,
|
|
248
|
+
WtRichTextEditor,
|
|
236
249
|
WtCheckbox,
|
|
237
250
|
WtDatepicker,
|
|
238
251
|
WtIconBtn,
|
|
@@ -364,6 +377,7 @@ export {
|
|
|
364
377
|
WtProgressBar,
|
|
365
378
|
WtRadio,
|
|
366
379
|
WtReplaceTransition,
|
|
380
|
+
WtRichTextEditor,
|
|
367
381
|
WtRoundedAction,
|
|
368
382
|
WtScreenRecordingsAction,
|
|
369
383
|
WtSearchBar,
|
package/src/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.compat.spec.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cc-workspaces runs on the @vue/compat runtime. There, a component given a
|
|
3
|
+
* `modelValue` prop has it rewritten to `value` / `onModelCompat:input` unless
|
|
4
|
+
* the component is marked as Vue 3 — and tinymce-vue binds the model only when
|
|
5
|
+
* `onUpdate:modelValue` is in its attrs, so it silently stops emitting
|
|
6
|
+
* (WTEL-4477). This runs the editor on the compat runtime (one instance of it:
|
|
7
|
+
* @vue/test-utils would bring the real `vue`, so mounting goes through compat's
|
|
8
|
+
* own createApp) to pin that the listener still arrives.
|
|
9
|
+
*/
|
|
10
|
+
vi.mock('vue', async () => await import('@vue/compat'));
|
|
11
|
+
|
|
12
|
+
vi.mock('tinymce/tinymce', () => ({}));
|
|
13
|
+
vi.mock('tinymce/icons/default', () => ({}));
|
|
14
|
+
vi.mock('tinymce/models/dom', () => ({}));
|
|
15
|
+
vi.mock('tinymce/themes/silver', () => ({}));
|
|
16
|
+
vi.mock('tinymce/plugins/advlist', () => ({}));
|
|
17
|
+
vi.mock('tinymce/plugins/emoticons', () => ({}));
|
|
18
|
+
vi.mock('tinymce/plugins/emoticons/js/emojis', () => ({}));
|
|
19
|
+
vi.mock('tinymce/plugins/fullscreen', () => ({}));
|
|
20
|
+
vi.mock('tinymce/plugins/image', () => ({}));
|
|
21
|
+
vi.mock('tinymce/plugins/link', () => ({}));
|
|
22
|
+
vi.mock('tinymce/plugins/lists', () => ({}));
|
|
23
|
+
vi.mock('tinymce/plugins/table', () => ({}));
|
|
24
|
+
vi.mock('tinymce/skins/ui/oxide/skin.css', () => ({}));
|
|
25
|
+
|
|
26
|
+
// Stands in for tinymce-vue's Editor: no declared emits, model read from attrs.
|
|
27
|
+
const seenAttrs = [];
|
|
28
|
+
const seenModel = [];
|
|
29
|
+
let emitEdit = () => {};
|
|
30
|
+
vi.mock('@tinymce/tinymce-vue', async () => {
|
|
31
|
+
const { defineComponent, h } = await import('@vue/compat');
|
|
32
|
+
return {
|
|
33
|
+
default: defineComponent({
|
|
34
|
+
name: 'Editor',
|
|
35
|
+
props: [
|
|
36
|
+
'id',
|
|
37
|
+
'modelValue',
|
|
38
|
+
'init',
|
|
39
|
+
'plugins',
|
|
40
|
+
'toolbar',
|
|
41
|
+
'outputFormat',
|
|
42
|
+
'disabled',
|
|
43
|
+
'licenseKey',
|
|
44
|
+
],
|
|
45
|
+
setup(props, ctx) {
|
|
46
|
+
seenAttrs.push(Object.keys(ctx.attrs));
|
|
47
|
+
seenModel.push(props.modelValue);
|
|
48
|
+
// lets a test type into the stand-in
|
|
49
|
+
emitEdit = (value) => ctx.attrs['onUpdate:modelValue']?.(value);
|
|
50
|
+
return () => h('div');
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const { configureCompat, createApp, h } = await import('@vue/compat');
|
|
57
|
+
const { default: TinyMceVueEditor } = await import('@tinymce/tinymce-vue');
|
|
58
|
+
const { default: WtRichTextEditor } = await import(
|
|
59
|
+
'../wt-rich-text-editor.vue'
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
async function render(component, props) {
|
|
63
|
+
const app = createApp({
|
|
64
|
+
render: () => h(component, props),
|
|
65
|
+
});
|
|
66
|
+
// the label is resolved globally in apps; not under test here
|
|
67
|
+
app.component('WtLabel', {
|
|
68
|
+
render: () => null,
|
|
69
|
+
});
|
|
70
|
+
app.mount(document.createElement('div'));
|
|
71
|
+
// the editor mounts once TinyMCE has loaded (mocked here: at once)
|
|
72
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
73
|
+
return app;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe('WtRichTextEditor on @vue/compat', () => {
|
|
77
|
+
beforeAll(() => {
|
|
78
|
+
configureCompat({
|
|
79
|
+
MODE: 2,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
afterAll(() => {
|
|
84
|
+
configureCompat({
|
|
85
|
+
MODE: 3,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
seenAttrs.length = 0;
|
|
91
|
+
seenModel.length = 0;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('control: compat takes the model listener away from plain tinymce-vue', async () => {
|
|
95
|
+
(
|
|
96
|
+
await render(TinyMceVueEditor, {
|
|
97
|
+
modelValue: '',
|
|
98
|
+
'onUpdate:modelValue': () => {},
|
|
99
|
+
})
|
|
100
|
+
).unmount();
|
|
101
|
+
|
|
102
|
+
expect(seenAttrs[0]).not.toContain('onUpdate:modelValue');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('keeps the model listener on its editor', async () => {
|
|
106
|
+
(
|
|
107
|
+
await render(WtRichTextEditor, {
|
|
108
|
+
modelValue: '',
|
|
109
|
+
'onUpdate:modelValue': () => {},
|
|
110
|
+
})
|
|
111
|
+
).unmount();
|
|
112
|
+
|
|
113
|
+
expect(seenAttrs[0]).toContain('onUpdate:modelValue');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('takes its own v-model on compat: the value in, edits out', async () => {
|
|
117
|
+
const onUpdate = vi.fn();
|
|
118
|
+
await render(WtRichTextEditor, {
|
|
119
|
+
modelValue: '<p>Seed</p>',
|
|
120
|
+
'onUpdate:modelValue': onUpdate,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(seenModel[0]).toBe('<p>Seed</p>');
|
|
124
|
+
emitEdit('<p>Edited</p>');
|
|
125
|
+
expect(onUpdate).toHaveBeenCalledWith('<p>Edited</p>');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { flushPromises, mount } from '@vue/test-utils';
|
|
2
|
+
import { nextTick } from 'vue';
|
|
3
|
+
|
|
4
|
+
// TinyMCE cannot run in jsdom: its side-effect imports are stubbed, and the
|
|
5
|
+
// Vue wrapper is replaced by a stub that records its props.
|
|
6
|
+
vi.mock('tinymce/tinymce', () => ({}));
|
|
7
|
+
vi.mock('tinymce/icons/default', () => ({}));
|
|
8
|
+
vi.mock('tinymce/models/dom', () => ({}));
|
|
9
|
+
vi.mock('tinymce/themes/silver', () => ({}));
|
|
10
|
+
vi.mock('tinymce/plugins/advlist', () => ({}));
|
|
11
|
+
vi.mock('tinymce/plugins/emoticons', () => ({}));
|
|
12
|
+
vi.mock('tinymce/plugins/emoticons/js/emojis', () => ({}));
|
|
13
|
+
vi.mock('tinymce/plugins/fullscreen', () => ({}));
|
|
14
|
+
vi.mock('tinymce/plugins/image', () => ({}));
|
|
15
|
+
vi.mock('tinymce/plugins/link', () => ({}));
|
|
16
|
+
vi.mock('tinymce/plugins/lists', () => ({}));
|
|
17
|
+
vi.mock('tinymce/plugins/table', () => ({}));
|
|
18
|
+
vi.mock('tinymce/skins/ui/oxide/skin.css', () => ({}));
|
|
19
|
+
// setup-based like the real Editor, so wrapping it the wrong way shows up
|
|
20
|
+
vi.mock('@tinymce/tinymce-vue', async () => {
|
|
21
|
+
const { h } = await import('vue');
|
|
22
|
+
return {
|
|
23
|
+
default: {
|
|
24
|
+
name: 'Editor',
|
|
25
|
+
props: [
|
|
26
|
+
'id',
|
|
27
|
+
'modelValue',
|
|
28
|
+
'init',
|
|
29
|
+
'plugins',
|
|
30
|
+
'toolbar',
|
|
31
|
+
'outputFormat',
|
|
32
|
+
'disabled',
|
|
33
|
+
'licenseKey',
|
|
34
|
+
],
|
|
35
|
+
emits: [
|
|
36
|
+
'update:modelValue',
|
|
37
|
+
'init',
|
|
38
|
+
],
|
|
39
|
+
setup: () => () =>
|
|
40
|
+
h('div', {
|
|
41
|
+
class: 'editor-stub',
|
|
42
|
+
}),
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const { default: WtRichTextEditor } = await import(
|
|
48
|
+
'../wt-rich-text-editor.vue'
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// the editor mounts once TinyMCE has loaded (mocked here: at once)
|
|
52
|
+
const mountEditor = async (props = {}) => {
|
|
53
|
+
const wrapper = mount(WtRichTextEditor, {
|
|
54
|
+
props,
|
|
55
|
+
});
|
|
56
|
+
await flushPromises();
|
|
57
|
+
return wrapper;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const editorStub = (wrapper) =>
|
|
61
|
+
wrapper.findComponent({
|
|
62
|
+
name: 'TinyMceEditor',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('WtRichTextEditor', () => {
|
|
66
|
+
afterEach(() => {
|
|
67
|
+
document.documentElement.classList.remove('theme--dark');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('shows a label only when given one', async () => {
|
|
71
|
+
expect((await mountEditor()).find('.wt-label').exists()).toBe(false);
|
|
72
|
+
const labelled = await mountEditor({
|
|
73
|
+
label: 'Reply',
|
|
74
|
+
});
|
|
75
|
+
expect(labelled.text()).toContain('Reply');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('edits html with the full toolbar by default', async () => {
|
|
79
|
+
const editor = editorStub(await mountEditor());
|
|
80
|
+
|
|
81
|
+
expect(editor.props('outputFormat')).toBe('html');
|
|
82
|
+
expect(editor.props('plugins')).toContain('table');
|
|
83
|
+
expect(editor.props('toolbar')).toContain('bold italic');
|
|
84
|
+
expect(editor.props('init').height).toBe(300);
|
|
85
|
+
expect(editor.props('licenseKey')).toBe('gpl');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('drops the formatting tools for plain text', async () => {
|
|
89
|
+
const editor = editorStub(
|
|
90
|
+
await mountEditor({
|
|
91
|
+
output: 'text',
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(editor.props('plugins')).toEqual([]);
|
|
96
|
+
expect(editor.props('toolbar')).toBe('undo redo');
|
|
97
|
+
expect(editor.props('init').statusbar).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('takes toolbar and plugin overrides', async () => {
|
|
101
|
+
const editor = editorStub(
|
|
102
|
+
await mountEditor({
|
|
103
|
+
toolbar: 'bold italic | link',
|
|
104
|
+
plugins: [
|
|
105
|
+
'link',
|
|
106
|
+
],
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
expect(editor.props('toolbar')).toBe('bold italic | link');
|
|
111
|
+
expect(editor.props('plugins')).toEqual([
|
|
112
|
+
'link',
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('passes the model through both ways', async () => {
|
|
117
|
+
const wrapper = await mountEditor({
|
|
118
|
+
modelValue: '<p>Hi</p>',
|
|
119
|
+
});
|
|
120
|
+
expect(editorStub(wrapper).props('modelValue')).toBe('<p>Hi</p>');
|
|
121
|
+
|
|
122
|
+
await editorStub(wrapper).vm.$emit('update:modelValue', '<p>Hello</p>');
|
|
123
|
+
|
|
124
|
+
expect(wrapper.emitted('update:modelValue')?.[0]).toEqual([
|
|
125
|
+
'<p>Hello</p>',
|
|
126
|
+
]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('restyles the editable body when the app switches theme', async () => {
|
|
130
|
+
const body = {};
|
|
131
|
+
const setStyles = vi.fn();
|
|
132
|
+
const wrapper = await mountEditor();
|
|
133
|
+
|
|
134
|
+
editorStub(wrapper).vm.$emit(
|
|
135
|
+
'init',
|
|
136
|
+
{},
|
|
137
|
+
{
|
|
138
|
+
getBody: () => body,
|
|
139
|
+
dom: {
|
|
140
|
+
setStyles,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
document.documentElement.classList.add('theme--dark');
|
|
145
|
+
// MutationObserver callbacks run as microtasks
|
|
146
|
+
await nextTick();
|
|
147
|
+
await Promise.resolve();
|
|
148
|
+
|
|
149
|
+
expect(setStyles).toHaveBeenCalledWith(
|
|
150
|
+
body,
|
|
151
|
+
expect.objectContaining({
|
|
152
|
+
backgroundColor: expect.any(String),
|
|
153
|
+
color: expect.any(String),
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const { loaded, track } = vi.hoisted(() => {
|
|
2
|
+
const loaded = [];
|
|
3
|
+
return {
|
|
4
|
+
loaded,
|
|
5
|
+
track: (name) => () => {
|
|
6
|
+
loaded.push(name);
|
|
7
|
+
return {};
|
|
8
|
+
},
|
|
9
|
+
};
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
vi.mock('tinymce/tinymce', track('core'));
|
|
13
|
+
vi.mock('tinymce/icons/default', track('icons'));
|
|
14
|
+
vi.mock('tinymce/models/dom', track('model'));
|
|
15
|
+
vi.mock('tinymce/themes/silver', track('theme'));
|
|
16
|
+
vi.mock('tinymce/plugins/advlist', track('advlist'));
|
|
17
|
+
vi.mock('tinymce/plugins/emoticons', track('emoticons'));
|
|
18
|
+
vi.mock('tinymce/plugins/emoticons/js/emojis', track('emojis'));
|
|
19
|
+
vi.mock('tinymce/plugins/fullscreen', track('fullscreen'));
|
|
20
|
+
vi.mock('tinymce/plugins/image', track('image'));
|
|
21
|
+
vi.mock('tinymce/plugins/link', track('link'));
|
|
22
|
+
vi.mock('tinymce/plugins/lists', track('lists'));
|
|
23
|
+
vi.mock('tinymce/plugins/table', track('table'));
|
|
24
|
+
vi.mock('tinymce/skins/ui/oxide/skin.css', track('skin'));
|
|
25
|
+
|
|
26
|
+
const { loadTinyMce } = await import('../_internals/loadTinyMce');
|
|
27
|
+
|
|
28
|
+
describe('loadTinyMce', () => {
|
|
29
|
+
it('evaluates the core before any add-on that registers on it', async () => {
|
|
30
|
+
await loadTinyMce();
|
|
31
|
+
|
|
32
|
+
expect(loaded[0]).toBe('core');
|
|
33
|
+
expect(loaded).toEqual(
|
|
34
|
+
expect.arrayContaining([
|
|
35
|
+
'icons',
|
|
36
|
+
'model',
|
|
37
|
+
'theme',
|
|
38
|
+
'table',
|
|
39
|
+
'skin',
|
|
40
|
+
]),
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('loads once, however many editors ask', async () => {
|
|
45
|
+
const count = loaded.length;
|
|
46
|
+
|
|
47
|
+
await Promise.all([
|
|
48
|
+
loadTinyMce(),
|
|
49
|
+
loadTinyMce(),
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
expect(loaded).toHaveLength(count);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads TinyMCE (self-hosted) once, core first.
|
|
3
|
+
*
|
|
4
|
+
* The icons, model, theme and plugins register themselves on the global
|
|
5
|
+
* `tinymce` the core sets, so the core must run before any of them. Plain
|
|
6
|
+
* side-effect imports do not guarantee that: in an app's production build
|
|
7
|
+
* the core — it touches `module` — ends up behind a lazy CommonJS wrapper,
|
|
8
|
+
* while the add-ons, plain IIFEs, are hoisted and run first, failing with
|
|
9
|
+
* "tinymce is not defined". Awaiting the core's import pins the order.
|
|
10
|
+
*/
|
|
11
|
+
let loading: Promise<void> | null = null;
|
|
12
|
+
|
|
13
|
+
export function loadTinyMce(): Promise<void> {
|
|
14
|
+
loading ??= import('tinymce/tinymce').then(() =>
|
|
15
|
+
Promise.all([
|
|
16
|
+
import('tinymce/icons/default'),
|
|
17
|
+
import('tinymce/models/dom'),
|
|
18
|
+
import('tinymce/themes/silver'),
|
|
19
|
+
import('tinymce/plugins/advlist'),
|
|
20
|
+
import('tinymce/plugins/emoticons'),
|
|
21
|
+
import('tinymce/plugins/emoticons/js/emojis'),
|
|
22
|
+
import('tinymce/plugins/fullscreen'),
|
|
23
|
+
import('tinymce/plugins/image'),
|
|
24
|
+
import('tinymce/plugins/link'),
|
|
25
|
+
import('tinymce/plugins/lists'),
|
|
26
|
+
import('tinymce/plugins/table'),
|
|
27
|
+
import('tinymce/skins/ui/oxide/skin.css'),
|
|
28
|
+
]).then(() => undefined),
|
|
29
|
+
);
|
|
30
|
+
return loading;
|
|
31
|
+
}
|