@webitel/ui-sdk 26.8.119 → 26.8.121
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-CjVXyqY8.js → clients-CwGTZ_pb.js} +1 -1
- package/dist/{components-jZ2GzWbv.js → components-Bl2fDyms.js} +709 -718
- package/dist/ui-sdk.css +1 -1
- package/dist/ui-sdk.js +1 -1
- package/dist/ui-sdk.umd.cjs +2117 -2117
- package/dist/{wt-chat-emoji-RHMx_k1y.js → wt-chat-emoji-CQ-ZUYcm.js} +1 -1
- package/dist/{wt-display-chip-items-D_iKQXSB.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-B2lqrtGF.js → wt-send-message-popup-Ig3Uq6So.js} +2 -2
- package/dist/{wt-type-extension-value-input-CJWynqhC.js → wt-type-extension-value-input-B4fjVEic.js} +2 -2
- package/dist/{wt-vidstack-player-BL6xL6yo.js → wt-vidstack-player-DitE8XQx.js} +1 -1
- package/package.json +1 -1
- package/src/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.compat.spec.js +19 -13
- package/src/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.spec.js +21 -18
- 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/wt-rich-text-editor.vue +15 -17
- package/src/modules/AgentPdfs/components/agent-pdfs-tab.vue +6 -0
- package/types/.tsbuildinfo +1 -1
- 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/wt-rich-text-editor.vue.d.ts +0 -13
- package/dist/wt-label-unpxAKnu.js +0 -28
- package/dist/wt-rich-text-editor-B-iHNpqD.js +0 -119
|
@@ -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-CJWynqhC.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
package/src/components/on-demand/wt-rich-text-editor/__tests__/WtRichTextEditor.compat.spec.js
CHANGED
|
@@ -59,7 +59,7 @@ const { default: WtRichTextEditor } = await import(
|
|
|
59
59
|
'../wt-rich-text-editor.vue'
|
|
60
60
|
);
|
|
61
61
|
|
|
62
|
-
function render(component, props) {
|
|
62
|
+
async function render(component, props) {
|
|
63
63
|
const app = createApp({
|
|
64
64
|
render: () => h(component, props),
|
|
65
65
|
});
|
|
@@ -68,6 +68,8 @@ function render(component, props) {
|
|
|
68
68
|
render: () => null,
|
|
69
69
|
});
|
|
70
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));
|
|
71
73
|
return app;
|
|
72
74
|
}
|
|
73
75
|
|
|
@@ -89,27 +91,31 @@ describe('WtRichTextEditor on @vue/compat', () => {
|
|
|
89
91
|
seenModel.length = 0;
|
|
90
92
|
});
|
|
91
93
|
|
|
92
|
-
it('control: compat takes the model listener away from plain tinymce-vue', () => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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();
|
|
97
101
|
|
|
98
102
|
expect(seenAttrs[0]).not.toContain('onUpdate:modelValue');
|
|
99
103
|
});
|
|
100
104
|
|
|
101
|
-
it('keeps the model listener on its editor', () => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
it('keeps the model listener on its editor', async () => {
|
|
106
|
+
(
|
|
107
|
+
await render(WtRichTextEditor, {
|
|
108
|
+
modelValue: '',
|
|
109
|
+
'onUpdate:modelValue': () => {},
|
|
110
|
+
})
|
|
111
|
+
).unmount();
|
|
106
112
|
|
|
107
113
|
expect(seenAttrs[0]).toContain('onUpdate:modelValue');
|
|
108
114
|
});
|
|
109
115
|
|
|
110
|
-
it('takes its own v-model on compat: the value in, edits out', () => {
|
|
116
|
+
it('takes its own v-model on compat: the value in, edits out', async () => {
|
|
111
117
|
const onUpdate = vi.fn();
|
|
112
|
-
render(WtRichTextEditor, {
|
|
118
|
+
await render(WtRichTextEditor, {
|
|
113
119
|
modelValue: '<p>Seed</p>',
|
|
114
120
|
'onUpdate:modelValue': onUpdate,
|
|
115
121
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mount } from '@vue/test-utils';
|
|
1
|
+
import { flushPromises, mount } from '@vue/test-utils';
|
|
2
2
|
import { nextTick } from 'vue';
|
|
3
3
|
|
|
4
4
|
// TinyMCE cannot run in jsdom: its side-effect imports are stubbed, and the
|
|
@@ -48,10 +48,14 @@ const { default: WtRichTextEditor } = await import(
|
|
|
48
48
|
'../wt-rich-text-editor.vue'
|
|
49
49
|
);
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// the editor mounts once TinyMCE has loaded (mocked here: at once)
|
|
52
|
+
const mountEditor = async (props = {}) => {
|
|
53
|
+
const wrapper = mount(WtRichTextEditor, {
|
|
53
54
|
props,
|
|
54
55
|
});
|
|
56
|
+
await flushPromises();
|
|
57
|
+
return wrapper;
|
|
58
|
+
};
|
|
55
59
|
|
|
56
60
|
const editorStub = (wrapper) =>
|
|
57
61
|
wrapper.findComponent({
|
|
@@ -63,17 +67,16 @@ describe('WtRichTextEditor', () => {
|
|
|
63
67
|
document.documentElement.classList.remove('theme--dark');
|
|
64
68
|
});
|
|
65
69
|
|
|
66
|
-
it('shows a label only when given one', () => {
|
|
67
|
-
expect(mountEditor().find('.wt-label').exists()).toBe(false);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
).toContain('Reply');
|
|
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');
|
|
73
76
|
});
|
|
74
77
|
|
|
75
|
-
it('edits html with the full toolbar by default', () => {
|
|
76
|
-
const editor = editorStub(mountEditor());
|
|
78
|
+
it('edits html with the full toolbar by default', async () => {
|
|
79
|
+
const editor = editorStub(await mountEditor());
|
|
77
80
|
|
|
78
81
|
expect(editor.props('outputFormat')).toBe('html');
|
|
79
82
|
expect(editor.props('plugins')).toContain('table');
|
|
@@ -82,9 +85,9 @@ describe('WtRichTextEditor', () => {
|
|
|
82
85
|
expect(editor.props('licenseKey')).toBe('gpl');
|
|
83
86
|
});
|
|
84
87
|
|
|
85
|
-
it('drops the formatting tools for plain text', () => {
|
|
88
|
+
it('drops the formatting tools for plain text', async () => {
|
|
86
89
|
const editor = editorStub(
|
|
87
|
-
mountEditor({
|
|
90
|
+
await mountEditor({
|
|
88
91
|
output: 'text',
|
|
89
92
|
}),
|
|
90
93
|
);
|
|
@@ -94,9 +97,9 @@ describe('WtRichTextEditor', () => {
|
|
|
94
97
|
expect(editor.props('init').statusbar).toBe(false);
|
|
95
98
|
});
|
|
96
99
|
|
|
97
|
-
it('takes toolbar and plugin overrides', () => {
|
|
100
|
+
it('takes toolbar and plugin overrides', async () => {
|
|
98
101
|
const editor = editorStub(
|
|
99
|
-
mountEditor({
|
|
102
|
+
await mountEditor({
|
|
100
103
|
toolbar: 'bold italic | link',
|
|
101
104
|
plugins: [
|
|
102
105
|
'link',
|
|
@@ -111,7 +114,7 @@ describe('WtRichTextEditor', () => {
|
|
|
111
114
|
});
|
|
112
115
|
|
|
113
116
|
it('passes the model through both ways', async () => {
|
|
114
|
-
const wrapper = mountEditor({
|
|
117
|
+
const wrapper = await mountEditor({
|
|
115
118
|
modelValue: '<p>Hi</p>',
|
|
116
119
|
});
|
|
117
120
|
expect(editorStub(wrapper).props('modelValue')).toBe('<p>Hi</p>');
|
|
@@ -126,7 +129,7 @@ describe('WtRichTextEditor', () => {
|
|
|
126
129
|
it('restyles the editable body when the app switches theme', async () => {
|
|
127
130
|
const body = {};
|
|
128
131
|
const setStyles = vi.fn();
|
|
129
|
-
const wrapper = mountEditor();
|
|
132
|
+
const wrapper = await mountEditor();
|
|
130
133
|
|
|
131
134
|
editorStub(wrapper).vm.$emit(
|
|
132
135
|
'init',
|
|
@@ -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
|
+
}
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
{{ label }}
|
|
15
15
|
</slot>
|
|
16
16
|
</wt-label>
|
|
17
|
+
<!-- tinymce-vue falls back to Tiny's cloud CDN when window.tinymce is
|
|
18
|
+
missing, so the editor only mounts once the self-hosted copy is in -->
|
|
17
19
|
<tiny-mce-editor
|
|
20
|
+
v-if="isTinyMceLoaded"
|
|
18
21
|
:id="editorId"
|
|
19
22
|
v-model="model"
|
|
20
23
|
:disabled="disabled"
|
|
@@ -25,33 +28,23 @@
|
|
|
25
28
|
license-key="gpl"
|
|
26
29
|
@init="handleInit"
|
|
27
30
|
/>
|
|
31
|
+
<wt-rich-text-editor-skeleton v-else />
|
|
28
32
|
</div>
|
|
29
33
|
</template>
|
|
30
34
|
|
|
31
35
|
<script setup lang="ts">
|
|
32
36
|
/**
|
|
33
|
-
* TinyMCE, self-hosted
|
|
34
|
-
*
|
|
35
|
-
*
|
|
37
|
+
* TinyMCE, self-hosted, loads with this component only: `components/index.js`
|
|
38
|
+
* registers it through `defineAsyncComponent`, and TinyMCE itself is loaded
|
|
39
|
+
* by `loadTinyMce` (in order) once the component is created.
|
|
36
40
|
*/
|
|
37
41
|
import TinyMceVueEditor from '@tinymce/tinymce-vue';
|
|
38
42
|
import type { Editor as TinyMceInstance } from 'tinymce';
|
|
39
|
-
import '
|
|
40
|
-
import 'tinymce/icons/default';
|
|
41
|
-
import 'tinymce/models/dom';
|
|
42
|
-
import 'tinymce/themes/silver';
|
|
43
|
-
import 'tinymce/plugins/advlist';
|
|
44
|
-
import 'tinymce/plugins/emoticons';
|
|
45
|
-
import 'tinymce/plugins/emoticons/js/emojis';
|
|
46
|
-
import 'tinymce/plugins/fullscreen';
|
|
47
|
-
import 'tinymce/plugins/image';
|
|
48
|
-
import 'tinymce/plugins/link';
|
|
49
|
-
import 'tinymce/plugins/lists';
|
|
50
|
-
import 'tinymce/plugins/table';
|
|
51
|
-
import 'tinymce/skins/ui/oxide/skin.css';
|
|
52
|
-
import { computed, onBeforeUnmount, useId } from 'vue';
|
|
43
|
+
import { computed, onBeforeUnmount, ref, useId } from 'vue';
|
|
53
44
|
|
|
54
45
|
import WtLabel from '../../wt-label/wt-label.vue';
|
|
46
|
+
import { loadTinyMce } from './_internals/loadTinyMce';
|
|
47
|
+
import WtRichTextEditorSkeleton from './_internals/wt-rich-text-editor-skeleton.vue';
|
|
55
48
|
|
|
56
49
|
/**
|
|
57
50
|
* tinymce-vue binds the model only when it finds `onUpdate:modelValue` in its
|
|
@@ -128,6 +121,11 @@ const model = defineModel<string>();
|
|
|
128
121
|
|
|
129
122
|
const editorId = useId();
|
|
130
123
|
|
|
124
|
+
const isTinyMceLoaded = ref(false);
|
|
125
|
+
loadTinyMce().then(() => {
|
|
126
|
+
isTinyMceLoaded.value = true;
|
|
127
|
+
});
|
|
128
|
+
|
|
131
129
|
const HTML_PLUGINS = [
|
|
132
130
|
'advlist',
|
|
133
131
|
'emoticons',
|
|
@@ -35,8 +35,12 @@
|
|
|
35
35
|
:headers="shownHeaders"
|
|
36
36
|
:selected="selected"
|
|
37
37
|
sortable
|
|
38
|
+
resizable-columns
|
|
39
|
+
reorderable-columns
|
|
38
40
|
@sort="updateSort"
|
|
39
41
|
@update:selected="updateSelected"
|
|
42
|
+
@column-resize="columnResize"
|
|
43
|
+
@column-reorder="columnReorder"
|
|
40
44
|
>
|
|
41
45
|
<template #preview="{ item }">
|
|
42
46
|
<pdf-status-preview
|
|
@@ -175,6 +179,8 @@ const {
|
|
|
175
179
|
updateSort,
|
|
176
180
|
hasFilter,
|
|
177
181
|
addFilter,
|
|
182
|
+
columnResize,
|
|
183
|
+
columnReorder,
|
|
178
184
|
} = tableStore;
|
|
179
185
|
|
|
180
186
|
initialize();
|