aloha-vue 1.2.64 → 1.2.65-a2
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/package.json
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ref,
|
|
3
|
+
} from "vue";
|
|
4
|
+
|
|
5
|
+
export const tinymcePluginOptions = ref({
|
|
6
|
+
propsDefault: {
|
|
7
|
+
branding: true,
|
|
8
|
+
contentCustomStyle: undefined,
|
|
9
|
+
contentLangs: [
|
|
10
|
+
{ title: "English", code: "en" },
|
|
11
|
+
{ title: "Spanish", code: "es" },
|
|
12
|
+
{ title: "French", code: "fr" },
|
|
13
|
+
{ title: "Deutsch", code: "de" },
|
|
14
|
+
{ title: "Portuguese", code: "pt" },
|
|
15
|
+
{ title: "Chinese", code: "zh" },
|
|
16
|
+
],
|
|
17
|
+
languageDefault: "de",
|
|
18
|
+
maxlength: undefined,
|
|
19
|
+
menu: undefined,
|
|
20
|
+
menubar: false,
|
|
21
|
+
plugins: "advlist code emoticons link lists table help example",
|
|
22
|
+
promotion: false,
|
|
23
|
+
rows: undefined,
|
|
24
|
+
toolbar: [
|
|
25
|
+
{ name: "Formatierung", items: ["bold", "italic", "underline"] },
|
|
26
|
+
{ name: "Ausrichtung", items: ["alignleft", "aligncenter", "alignright", "alignjustify"] },
|
|
27
|
+
{ name: "Liste", items: ["bullist", "numlist"] },
|
|
28
|
+
{ name: "Einrücken", items: ["indent", "outdent"] },
|
|
29
|
+
{ name: "Sprache", items: ["language"] },
|
|
30
|
+
{ name: "Link", items: ["link", "unlink"] },
|
|
31
|
+
{ name: "Historie", items: ["undo", "redo"] },
|
|
32
|
+
{ name: "Hilfe", items: ["help"] },
|
|
33
|
+
],
|
|
34
|
+
toolbarMode: "wrap",
|
|
35
|
+
validElements: "@[style],a[href|target|title],strong/b[style],em/i[style],div[style],br[style],p[style],span[style],ul[style],ol[style],li[style],table[],thead[],tbody[],th[],tr[],td[]",
|
|
36
|
+
validStyles: {
|
|
37
|
+
"*": "text-align,padding-left,text-decoration",
|
|
38
|
+
ul: "list-style-type",
|
|
39
|
+
ol: "list-style-type",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export default {
|
|
45
|
+
install: (app, {
|
|
46
|
+
propsDefault = {},
|
|
47
|
+
} = {}) => {
|
|
48
|
+
tinymcePluginOptions.value.propsDefault = {
|
|
49
|
+
...tinymcePluginOptions.value.propsDefault,
|
|
50
|
+
...propsDefault,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -15,6 +15,10 @@ import ATinymceAPI from "./compositionAPI/ATinymceAPI";
|
|
|
15
15
|
import UiAPI from "../compositionApi/UiAPI";
|
|
16
16
|
import UiStyleHideAPI from "../compositionApi/UiStyleHideAPI";
|
|
17
17
|
|
|
18
|
+
import {
|
|
19
|
+
tinymcePluginOptions,
|
|
20
|
+
} from "../../plugins/ATinymcePlugin";
|
|
21
|
+
|
|
18
22
|
export default {
|
|
19
23
|
name: "ATinymce",
|
|
20
24
|
mixins: [
|
|
@@ -24,71 +28,62 @@ export default {
|
|
|
24
28
|
branding: {
|
|
25
29
|
type: Boolean,
|
|
26
30
|
required: false,
|
|
27
|
-
default:
|
|
31
|
+
default: () => tinymcePluginOptions.value.propsDefault.branding,
|
|
32
|
+
},
|
|
33
|
+
contentCustomStyle: {
|
|
34
|
+
type: String,
|
|
35
|
+
required: false,
|
|
36
|
+
default: () => tinymcePluginOptions.value.propsDefault.contentCustomStyle,
|
|
28
37
|
},
|
|
29
38
|
contentLangs: {
|
|
30
39
|
type: Array,
|
|
31
40
|
required: false,
|
|
32
|
-
default: () =>
|
|
33
|
-
{ title: "English", code: "en" },
|
|
34
|
-
{ title: "Spanish", code: "es" },
|
|
35
|
-
{ title: "French", code: "fr" },
|
|
36
|
-
{ title: "Deutsch", code: "de" },
|
|
37
|
-
{ title: "Portuguese", code: "pt" },
|
|
38
|
-
{ title: "Chinese", code: "zh" },
|
|
39
|
-
],
|
|
41
|
+
default: () => tinymcePluginOptions.value.propsDefault.contentLangs,
|
|
40
42
|
},
|
|
41
43
|
languageDefault: {
|
|
42
44
|
type: String,
|
|
43
45
|
required: false,
|
|
44
|
-
default:
|
|
46
|
+
default: () => tinymcePluginOptions.value.propsDefault.languageDefault,
|
|
45
47
|
},
|
|
46
48
|
maxlength: {
|
|
47
49
|
type: [String, Number],
|
|
48
50
|
required: false,
|
|
51
|
+
default: () => tinymcePluginOptions.value.propsDefault.maxlength,
|
|
49
52
|
},
|
|
50
53
|
menu: {
|
|
51
54
|
type: Object,
|
|
52
55
|
required: false,
|
|
53
|
-
default:
|
|
56
|
+
default: () => tinymcePluginOptions.value.propsDefault.menu,
|
|
54
57
|
},
|
|
55
58
|
menubar: {
|
|
56
59
|
type: [String, Boolean],
|
|
57
60
|
required: false,
|
|
58
|
-
default:
|
|
61
|
+
default: () => tinymcePluginOptions.value.propsDefault.menubar,
|
|
59
62
|
},
|
|
60
63
|
plugins: {
|
|
61
64
|
type: String,
|
|
62
65
|
required: false,
|
|
63
|
-
default:
|
|
66
|
+
default: () => tinymcePluginOptions.value.propsDefault.plugins,
|
|
64
67
|
},
|
|
65
68
|
promotion: {
|
|
66
69
|
type: Boolean,
|
|
67
70
|
required: false,
|
|
68
|
-
default:
|
|
71
|
+
default: () => tinymcePluginOptions.value.propsDefault.promotion,
|
|
69
72
|
},
|
|
70
73
|
rows: {
|
|
71
74
|
type: [String, Number],
|
|
72
75
|
required: false,
|
|
76
|
+
default: () => tinymcePluginOptions.value.propsDefault.rows,
|
|
73
77
|
},
|
|
74
78
|
toolbar: {
|
|
75
79
|
type: [String, Object, Array],
|
|
76
80
|
required: false,
|
|
77
|
-
default: () =>
|
|
78
|
-
{ name: "Formatierung", items: ["bold", "italic", "underline"] },
|
|
79
|
-
{ name: "Ausrichtung", items: ["alignleft", "aligncenter", "alignright", "alignjustify"] },
|
|
80
|
-
{ name: "Liste", items: ["bullist", "numlist"] },
|
|
81
|
-
{ name: "Einrücken", items: ["indent", "outdent"] },
|
|
82
|
-
{ name: "Sprache", items: ["language"] },
|
|
83
|
-
{ name: "Link", items: ["link", "unlink"] },
|
|
84
|
-
{ name: "Historie", items: ["undo", "redo"] },
|
|
85
|
-
{ name: "Hilfe", items: ["help"] },
|
|
86
|
-
]),
|
|
81
|
+
default: () => tinymcePluginOptions.value.propsDefault.toolbar,
|
|
87
82
|
},
|
|
88
83
|
toolbarMode: {
|
|
89
84
|
type: String,
|
|
90
85
|
required: false,
|
|
91
|
-
default:
|
|
86
|
+
default: () => tinymcePluginOptions.value.propsDefault.toolbarMode,
|
|
92
87
|
validator: value => ["floating", "sliding", "scrolling", "wrap"].indexOf(value) !== -1,
|
|
93
88
|
},
|
|
94
89
|
type: {
|
|
@@ -96,6 +91,16 @@ export default {
|
|
|
96
91
|
required: false,
|
|
97
92
|
default: "tinymce",
|
|
98
93
|
},
|
|
94
|
+
validElements: {
|
|
95
|
+
type: String,
|
|
96
|
+
required: false,
|
|
97
|
+
default: () => tinymcePluginOptions.value.propsDefault.validElements,
|
|
98
|
+
},
|
|
99
|
+
validStyles: {
|
|
100
|
+
type: Object,
|
|
101
|
+
required: false,
|
|
102
|
+
default: () => tinymcePluginOptions.value.propsDefault.validStyles,
|
|
103
|
+
},
|
|
99
104
|
},
|
|
100
105
|
setup(props, context) {
|
|
101
106
|
const {
|
|
@@ -5,8 +5,11 @@ import {
|
|
|
5
5
|
watch,
|
|
6
6
|
} from "vue";
|
|
7
7
|
|
|
8
|
+
import MyPowerPaste from "../plugins/MyPowerPaste";
|
|
9
|
+
|
|
8
10
|
/* Import TinyMCE */
|
|
9
11
|
import tinymce from "tinymce";
|
|
12
|
+
tinymce.PluginManager.add("example", MyPowerPaste);
|
|
10
13
|
|
|
11
14
|
/* Default icons are required. After that, import custom icons if applicable */
|
|
12
15
|
import "tinymce/icons/default";
|
|
@@ -45,20 +48,27 @@ export default function ATinymceAPI(props, context, {
|
|
|
45
48
|
htmlIdLocal = computed(() => ""),
|
|
46
49
|
}) {
|
|
47
50
|
const branding = toRef(props, "branding");
|
|
51
|
+
const contentCustomStyle = toRef(props, "contentCustomStyle");
|
|
48
52
|
const contentLangs = toRef(props, "contentLangs");
|
|
49
53
|
const disabled = toRef(props, "disabled");
|
|
50
54
|
const languageDefault = toRef(props, "languageDefault");
|
|
51
55
|
const menu = toRef(props, "menu");
|
|
52
56
|
const menubar = toRef(props, "menubar");
|
|
57
|
+
const modelValue = toRef(props, "modelValue");
|
|
53
58
|
const plugins = toRef(props, "plugins");
|
|
54
59
|
const promotion = toRef(props, "promotion");
|
|
55
60
|
const toolbar = toRef(props, "toolbar");
|
|
56
61
|
const toolbarMode = toRef(props, "toolbarMode");
|
|
57
|
-
const
|
|
62
|
+
const validElements = toRef(props, "validElements");
|
|
63
|
+
const validStyles = toRef(props, "validStyles");
|
|
58
64
|
|
|
59
65
|
let vueEditor = null;
|
|
60
66
|
let modelValueLocal = undefined;
|
|
61
67
|
|
|
68
|
+
const contentStyle = computed(() => {
|
|
69
|
+
return `${ contentUiSkinCss.toString() }\n${ contentCss.toString() }\n${ contentCustomStyle.value ? contentCustomStyle.value : "" }`;
|
|
70
|
+
});
|
|
71
|
+
|
|
62
72
|
const changeModelLocal = ({ model }) => {
|
|
63
73
|
modelValueLocal = model;
|
|
64
74
|
changeModel({ model });
|
|
@@ -74,7 +84,7 @@ export default function ATinymceAPI(props, context, {
|
|
|
74
84
|
skin: false,
|
|
75
85
|
content_css: false,
|
|
76
86
|
entity_encoding: "raw",
|
|
77
|
-
content_style:
|
|
87
|
+
content_style: contentStyle.value,
|
|
78
88
|
branding: branding.value,
|
|
79
89
|
promotion: promotion.value,
|
|
80
90
|
language: languageDefault.value,
|
|
@@ -82,6 +92,9 @@ export default function ATinymceAPI(props, context, {
|
|
|
82
92
|
menu: menu.value,
|
|
83
93
|
menubar: menubar.value,
|
|
84
94
|
readonly: !!disabled.value,
|
|
95
|
+
valid_elements: validElements.value,
|
|
96
|
+
valid_styles: validStyles.value,
|
|
97
|
+
|
|
85
98
|
setup: editor => {
|
|
86
99
|
vueEditor = editor;
|
|
87
100
|
editor.on("change input undo redo", () => {
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forEach,
|
|
3
|
+
} from "lodash-es";
|
|
4
|
+
|
|
5
|
+
export default function(editor) {
|
|
6
|
+
// Event-Handler für Einfügevorgänge
|
|
7
|
+
editor.on("paste", function(e) {
|
|
8
|
+
const clipboardData = (e.clipboardData || window.clipboardData);
|
|
9
|
+
e.stopPropagation();
|
|
10
|
+
e.preventDefault();
|
|
11
|
+
|
|
12
|
+
const PAGE_HTML = clipboardData.getData("text/html");
|
|
13
|
+
const HTML_FRAGMENT = extractTextBetween({
|
|
14
|
+
html: PAGE_HTML,
|
|
15
|
+
start: "<!--StartFragment-->",
|
|
16
|
+
end: "<!--EndFragment-->",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
if (isMsWord({ html: PAGE_HTML })) {
|
|
20
|
+
editor.insertContent(parseWord({ html: HTML_FRAGMENT }));
|
|
21
|
+
} else {
|
|
22
|
+
const cleanedHtml = HTML_FRAGMENT.replace(/\sdata-[a-z0-9-]+="[^"]*"/gi, "").replace(/\saria-[a-z0-9-]+="[^"]*"/gi, "");
|
|
23
|
+
|
|
24
|
+
editor.insertContent(cleanedHtml);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
/* Return the metadata for the help plugin */
|
|
28
|
+
return {
|
|
29
|
+
getMetadata: () => ({
|
|
30
|
+
name: "My power paste",
|
|
31
|
+
url: "https://ilia-brykin.github.io/aloha/#/"
|
|
32
|
+
})
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function extractTextBetween({ html, start, end }) {
|
|
37
|
+
const startIndex = html.indexOf(start);
|
|
38
|
+
const endIndex = html.indexOf(end);
|
|
39
|
+
|
|
40
|
+
if (startIndex !== -1 && endIndex !== -1) {
|
|
41
|
+
return html.substring(startIndex + start.length, endIndex).trim();
|
|
42
|
+
}
|
|
43
|
+
return html;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isMsWord({ html }) {
|
|
47
|
+
const HTML_TAG = extractTextBetween({
|
|
48
|
+
html,
|
|
49
|
+
start: "<html",
|
|
50
|
+
end: ">",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return HTML_TAG.indexOf(":office:word") !== -1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseWord({ html }) {
|
|
57
|
+
const parser = new DOMParser();
|
|
58
|
+
const doc = parser.parseFromString(html, "text/html");
|
|
59
|
+
const allChildren = doc.body.querySelectorAll(":scope > *");
|
|
60
|
+
|
|
61
|
+
const rootElement = document.createElement("div");
|
|
62
|
+
let currentList = null;
|
|
63
|
+
let lastLevel = 0;
|
|
64
|
+
let lastIndent = 0;
|
|
65
|
+
const listStack = [];
|
|
66
|
+
|
|
67
|
+
forEach(allChildren, element => {
|
|
68
|
+
const isTagP = element.tagName === "P";
|
|
69
|
+
if (!isTagP) {
|
|
70
|
+
rootElement.appendChild(element);
|
|
71
|
+
lastLevel = 0;
|
|
72
|
+
lastIndent = 0;
|
|
73
|
+
currentList = null;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let htmlContent = element.innerHTML.trim();
|
|
78
|
+
const IS_NORMAL_PARAGRAPH = isNormalParagraph({ element, htmlContent });
|
|
79
|
+
htmlContent = htmlContent
|
|
80
|
+
.replace(/<u>/g, "<span style=\"text-decoration: underline;\">")
|
|
81
|
+
.replace(/<\/u>/g, "</span>")
|
|
82
|
+
.replace(/<!--\[if !supportLists]-->.*?<!--\[endif]-->/gs, "")
|
|
83
|
+
.replace(/ /g, " ")
|
|
84
|
+
.replace(/^[\s·o§1-9]+[.)]?/g, "");
|
|
85
|
+
|
|
86
|
+
if (isTagP && IS_NORMAL_PARAGRAPH) {
|
|
87
|
+
const newParagraph = document.createElement("p");
|
|
88
|
+
newParagraph.innerHTML = htmlContent;
|
|
89
|
+
rootElement.appendChild(newParagraph);
|
|
90
|
+
lastLevel = 0;
|
|
91
|
+
lastIndent = 0;
|
|
92
|
+
currentList = null;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const MARGIN_LEFT = parseInt(element.style.marginLeft) || 0;
|
|
97
|
+
const LIST_LEVEL = getCurrentLevelForList(element);
|
|
98
|
+
const { type, style } = getListTypeAndStyleFromSpanContent(element);
|
|
99
|
+
|
|
100
|
+
if (!currentList || LIST_LEVEL > lastLevel || MARGIN_LEFT > lastIndent) {
|
|
101
|
+
const newList = document.createElement(type);
|
|
102
|
+
if (style) {
|
|
103
|
+
newList.style.listStyleType = style;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (currentList) {
|
|
107
|
+
currentList.appendChild(newList);
|
|
108
|
+
} else {
|
|
109
|
+
rootElement.appendChild(newList);
|
|
110
|
+
}
|
|
111
|
+
currentList = newList;
|
|
112
|
+
listStack.push({ element: newList, level: LIST_LEVEL, marginLeft: MARGIN_LEFT });
|
|
113
|
+
} else if (LIST_LEVEL < lastLevel) {
|
|
114
|
+
while (listStack.length > 1 && LIST_LEVEL < listStack[listStack.length - 1].level) {
|
|
115
|
+
listStack.pop();
|
|
116
|
+
currentList = listStack[listStack.length - 1].element;
|
|
117
|
+
}
|
|
118
|
+
} else if (MARGIN_LEFT < lastIndent && listStack.length > 1) {
|
|
119
|
+
while (listStack.length > 1 && MARGIN_LEFT < listStack[listStack.length - 1].marginLeft) {
|
|
120
|
+
listStack.pop();
|
|
121
|
+
currentList = listStack[listStack.length - 1].element;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const li = document.createElement("li");
|
|
126
|
+
li.innerHTML = htmlContent;
|
|
127
|
+
currentList.appendChild(li);
|
|
128
|
+
|
|
129
|
+
lastLevel = LIST_LEVEL;
|
|
130
|
+
lastIndent = MARGIN_LEFT;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return rootElement.innerHTML;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getListTypeAndStyleFromSpanContent(p) {
|
|
137
|
+
const spans = p.querySelectorAll("span");
|
|
138
|
+
let spanContent = "";
|
|
139
|
+
forEach(spans, span => {
|
|
140
|
+
const styleAttr = span.getAttribute("style");
|
|
141
|
+
if (styleAttr && styleAttr.includes("mso-list:Ignore")) {
|
|
142
|
+
spanContent = span.textContent || "";
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
spanContent = spanContent.trim();
|
|
148
|
+
if (!spanContent) {
|
|
149
|
+
return { type: "ul", style: "" };
|
|
150
|
+
}
|
|
151
|
+
// Check for ordered list with Arabic numerals or numbers within parentheses
|
|
152
|
+
if (/^\d+[.)]/.test(spanContent) || /^\(\d+\)/.test(spanContent)) {
|
|
153
|
+
return { type: "ol", style: "" }; // Default style for <ol>
|
|
154
|
+
}
|
|
155
|
+
// Check for ordered list with uppercase Roman numerals
|
|
156
|
+
if (/^[IVXLCDM]+\./.test(spanContent)) {
|
|
157
|
+
return { type: "ol", style: "upper-roman" };
|
|
158
|
+
}
|
|
159
|
+
// Check for ordered list with lowercase Roman numerals
|
|
160
|
+
if (/^[ivxlcdm]+\./.test(spanContent)) {
|
|
161
|
+
return { type: "ol", style: "lower-roman" };
|
|
162
|
+
}
|
|
163
|
+
// Check for ordered list with uppercase letters
|
|
164
|
+
if (/^[A-Z]+[.)]/.test(spanContent)) {
|
|
165
|
+
return { type: "ol", style: "upper-alpha" };
|
|
166
|
+
}
|
|
167
|
+
// Check for ordered list with lowercase letters
|
|
168
|
+
if (/^[a-z]+[.)]/.test(spanContent)) {
|
|
169
|
+
return { type: "ol", style: "lower-alpha" };
|
|
170
|
+
}
|
|
171
|
+
// Check for unordered list with various symbols
|
|
172
|
+
if (/^[·o§]/.test(spanContent)) {
|
|
173
|
+
// Determine list style based on the symbol
|
|
174
|
+
let style;
|
|
175
|
+
switch (spanContent.trim()[0]) {
|
|
176
|
+
case "·": style = "disc"; break;
|
|
177
|
+
case "o": style = "circle"; break;
|
|
178
|
+
case "§": style = "square"; break;
|
|
179
|
+
default: style = "";
|
|
180
|
+
}
|
|
181
|
+
return { type: "ul", style: style };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { type: "ul", style: "" };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function getCurrentLevelForList(p) {
|
|
188
|
+
const styleAttr = p.getAttribute("style");
|
|
189
|
+
let level = 1;
|
|
190
|
+
if (styleAttr) {
|
|
191
|
+
const levelMatch = styleAttr.match(/mso-list:l\d+ level(\d+)/);
|
|
192
|
+
if (levelMatch) {
|
|
193
|
+
level = parseInt(levelMatch[1], 10);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return level;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isNormalParagraph({ element, htmlContent }) {
|
|
201
|
+
return element.className.includes("MsoNormal") ||
|
|
202
|
+
(!element.className.includes("MsoListParagraph") &&
|
|
203
|
+
!/mso-list:/.test(htmlContent));
|
|
204
|
+
}
|