@ajaxjs/ui 1.0.7 → 1.1.2
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/components/form/FileUploader/FileUploader.js +104 -0
- package/dist/components/form/FileUploader/FileUploader.js.map +1 -0
- package/dist/components/form/FileUploader/FileUploader.less +55 -0
- package/dist/components/form/FileUploader/FileUploader.ts +121 -0
- package/dist/components/form/FileUploader/FileUploader.vue +26 -0
- package/dist/components/form/HtmlEditor/HtmlEditor.js +11 -4
- package/dist/components/form/HtmlEditor/HtmlEditor.js.map +1 -1
- package/dist/components/form/HtmlEditor/HtmlEditor.less +185 -5
- package/dist/components/form/HtmlEditor/HtmlEditor.ts +12 -4
- package/dist/components/form/HtmlEditor/HtmlEditor.vue +19 -19
- package/dist/components/widgets/ImageEnlarger.vue +77 -20
- package/package.json +7 -10
- package/.browserslistrc +0 -3
- package/.eslintrc.js +0 -25
- package/babel.config.js +0 -5
- package/dist/components/list/attachment-picture-list.js +0 -57
- package/dist/components/list/attachment-picture-list.js.map +0 -1
- package/dist/components/list/attachment-picture-list.ts +0 -57
- package/dist/components/list/list.js +0 -227
- package/dist/components/list/list.js.map +0 -1
- package/dist/components/list/list.ts +0 -261
- package/dist/components/widgets/AjAvatar.vue +0 -42
- package/public/favicon.ico +0 -0
- package/public/index.html +0 -24
- package/src/App.vue +0 -32
- package/src/assets/logo.png +0 -0
- package/src/components/form/AjSelect.vue +0 -33
- package/src/components/form/HtmlEditor/HtmlEditor.less +0 -165
- package/src/components/form/HtmlEditor/HtmlEditor.ts +0 -329
- package/src/components/form/HtmlEditor/HtmlEditor.vue +0 -70
- package/src/components/form/HtmlEditor/html-editor-HtmlSanitizer.js +0 -103
- package/src/components/form/TreeLikeSelect.vue +0 -125
- package/src/components/list/attachment-picture-list.ts +0 -57
- package/src/components/list/list.js +0 -227
- package/src/components/list/list.ts +0 -261
- package/src/components/widgets/AccordionMenu.vue +0 -138
- package/src/components/widgets/AdjustFontSize.vue +0 -66
- package/src/components/widgets/AjAvatar.vue +0 -42
- package/src/components/widgets/Article.vue +0 -49
- package/src/components/widgets/BaiduSearch.vue +0 -50
- package/src/components/widgets/Expander.vue +0 -65
- package/src/components/widgets/ImageEnlarger.vue +0 -42
- package/src/components/widgets/OpacityBanner.vue +0 -124
- package/src/components/widgets/ProcessLine.vue +0 -133
- package/src/globalDeclare/shims.d.ts +0 -4
- package/src/index.ts +0 -8
- package/src/main.ts +0 -12
- package/src/pages/Nav.vue +0 -23
- package/src/pages/common.less +0 -10
- package/src/pages/demo/Article.vue +0 -30
- package/src/pages/demo/Form.vue +0 -46
- package/src/pages/demo/Wdigets.vue +0 -168
- package/src/router/index.js +0 -39
- package/src/router/index.js.map +0 -1
- package/src/router/index.ts +0 -39
- package/src/shims-tsx.d.ts +0 -13
- package/src/shims-vue.d.ts +0 -4
- package/src/style/common-functions.less +0 -171
- package/src/style/reset.less +0 -18
- package/src/views/About.vue +0 -5
- package/src/views/Home.vue +0 -49
- package/src/views/desktop/Desktop.vue +0 -251
- package/src/views/desktop/Window.vue +0 -62
- package/src/views/desktop/desktop.md +0 -14
- package/tsconfig.json +0 -30
- package/vue.config.js +0 -17
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
props: {
|
|
3
|
-
vModel: String,
|
|
4
|
-
uploadImageActionUrl: String // 图片上传路径
|
|
5
|
-
},
|
|
6
|
-
data(): any {
|
|
7
|
-
return {
|
|
8
|
-
isShowCode: false, // 是否显示 HTML 源码
|
|
9
|
-
iframeEl: null,
|
|
10
|
-
sourceEditor: null,
|
|
11
|
-
};
|
|
12
|
-
},
|
|
13
|
-
mounted(): void {
|
|
14
|
-
this.iframeEl = <HTMLIFrameElement>this.$el.querySelector('iframe');
|
|
15
|
-
this.sourceEditor = <HTMLTextAreaElement>this.$el.querySelector('textarea');
|
|
16
|
-
|
|
17
|
-
(<Window>this.iframeEl.contentWindow).onload = (ev: Event) => { // 这个方法只能写在 onload 事件里面, 不写 onload 里还不执行
|
|
18
|
-
this.iframeDoc = (<Window>this.iframeEl.contentWindow).document;
|
|
19
|
-
this.iframeDoc.designMode = 'on';
|
|
20
|
-
this.iframeDoc.addEventListener('paste', onImagePaste.bind(this));// 直接剪切板粘贴上传图片
|
|
21
|
-
|
|
22
|
-
new MutationObserver((mutationsList: MutationRecord[], observer: MutationObserver) => {// 监听DOM树变化
|
|
23
|
-
if (!this.isShowCode) {
|
|
24
|
-
this.sourceEditor.value = this.iframeDoc.body.innerHTML;
|
|
25
|
-
this.$emit('on-change', this.sourceEditor.value)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
}).observe(this.iframeDoc.body, { attributes: true, childList: true, subtree: true, characterData: true });
|
|
29
|
-
|
|
30
|
-
this.setIframeBody(this.vModel);
|
|
31
|
-
// this.sourceEditor.value && this.setIframeBody(this.sourceEditor.value);// 有内容
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
this.sourceEditor.oninput = (ev: Event) => {
|
|
35
|
-
if (this.isShowCode) {
|
|
36
|
-
this.setIframeBody(this.sourceEditor.value);
|
|
37
|
-
this.$emit('on-change', this.sourceEditor.value)
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// this.uploadImgMgr = this.$refs.uploadLayer;
|
|
42
|
-
},
|
|
43
|
-
methods: {
|
|
44
|
-
/**
|
|
45
|
-
* 输入 HTML 内容
|
|
46
|
-
*
|
|
47
|
-
* @param html
|
|
48
|
-
*/
|
|
49
|
-
setIframeBody(html: string): void {
|
|
50
|
-
this.iframeDoc.body.innerHTML = html;
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* 获取内容的 HTML
|
|
55
|
-
*
|
|
56
|
-
* @param cleanWord 是否清理冗余标签
|
|
57
|
-
* @param encode 是否 URL 编码
|
|
58
|
-
*/
|
|
59
|
-
getValue(cleanWord: boolean, encode: boolean): string {
|
|
60
|
-
let result: string = this.iframeDoc.body.innerHTML;
|
|
61
|
-
|
|
62
|
-
if (cleanWord)
|
|
63
|
-
result = cleanPaste(result);
|
|
64
|
-
|
|
65
|
-
if (encode)
|
|
66
|
-
result = encodeURIComponent(result);
|
|
67
|
-
|
|
68
|
-
return result;
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
createLink(): void {
|
|
72
|
-
let result: string = prompt("请输入 URL 地址");
|
|
73
|
-
if (result)
|
|
74
|
-
this.format("createLink", result);
|
|
75
|
-
},
|
|
76
|
-
|
|
77
|
-
insertImage(): void {
|
|
78
|
-
// @ts-ignore
|
|
79
|
-
if (window.isCreate)
|
|
80
|
-
alert('请保存记录后再上传图片。');
|
|
81
|
-
else {
|
|
82
|
-
this.uploadImgMgr.show((json: any) => {
|
|
83
|
-
if (json && json.isOk)
|
|
84
|
-
this.format("insertImage", json.fullUrl);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* 清理冗余 HTML
|
|
91
|
-
*/
|
|
92
|
-
cleanHTML(): void {
|
|
93
|
-
// @ts-ignore
|
|
94
|
-
this.setIframeBody(HtmlSanitizer.SanitizeHtml(this.iframeDoc.body.innerHTML));
|
|
95
|
-
},
|
|
96
|
-
|
|
97
|
-
saveRemoteImage2Local(): void {
|
|
98
|
-
saveRemoteImage2Local.call(this);
|
|
99
|
-
},
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* 当工具条点击的时候触发
|
|
103
|
-
*
|
|
104
|
-
* @param ev
|
|
105
|
-
*/
|
|
106
|
-
onCmdClk(ev: Event): void {
|
|
107
|
-
let el: HTMLElement = <HTMLElement>ev.target,
|
|
108
|
-
clsName = <string>el.className.split(' ').shift();
|
|
109
|
-
|
|
110
|
-
this.format(clsName);
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* 通过 document.execCommand() 来操纵可编辑内容区域的元素
|
|
115
|
-
*
|
|
116
|
-
* @param type 命令的名称
|
|
117
|
-
* @param para 一些命令(例如 insertImage)需要额外的参数(insertImage 需要提供插入 image 的 url),默认为 null
|
|
118
|
-
*/
|
|
119
|
-
format(type: string, para?: string): void {
|
|
120
|
-
if (para)
|
|
121
|
-
this.iframeDoc.execCommand(type, false, para);
|
|
122
|
-
else
|
|
123
|
-
this.iframeDoc.execCommand(type, false);
|
|
124
|
-
|
|
125
|
-
(<Window>this.iframeEl.contentWindow).focus();
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* 选择字号大小
|
|
130
|
-
*
|
|
131
|
-
* @param ev
|
|
132
|
-
*/
|
|
133
|
-
onFontsizeChoserClk(ev: Event): void {
|
|
134
|
-
let el: HTMLElement = <HTMLElement>ev.target,
|
|
135
|
-
els = (<HTMLElement>ev.currentTarget).children;
|
|
136
|
-
|
|
137
|
-
let i: number, j: number;
|
|
138
|
-
for (i = 0, j = els.length; i < j; i++)
|
|
139
|
-
if (el == els[i])
|
|
140
|
-
break;
|
|
141
|
-
|
|
142
|
-
this.format('fontsize', i + "");
|
|
143
|
-
},
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* 选择字体
|
|
147
|
-
*
|
|
148
|
-
* @param ev
|
|
149
|
-
*/
|
|
150
|
-
onFontfamilyChoserClk(ev: Event): void {
|
|
151
|
-
let el: HTMLElement = <HTMLElement>ev.target;
|
|
152
|
-
this.format('fontname', el.innerHTML);
|
|
153
|
-
|
|
154
|
-
/* 如何解决点击之后马上隐藏面板?由于 js(单击事件) 没有控制 CSS 的 :hover 伪类的方法,故所以必须使用以下技巧:*/
|
|
155
|
-
let menuPanel: HTMLElement = <HTMLElement>el.parentNode;
|
|
156
|
-
menuPanel.style.display = 'none';
|
|
157
|
-
setTimeout(() => menuPanel.style.display = '', 300);
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* 创建颜色选择器
|
|
162
|
-
*/
|
|
163
|
-
createColorPickerHTML(): string {
|
|
164
|
-
let cl: string[] = ['00', '33', '66', '99', 'CC', 'FF'],
|
|
165
|
-
b: string, d: string, e: string, f: string,
|
|
166
|
-
h: string[] = ['<div class="colorhead"><span class="colortitle">颜色选择</span></div><div class="colorbody"><table cellspaci="0" cellpadding="0"><tr>'];
|
|
167
|
-
|
|
168
|
-
// 创建 body [6 x 6的色盘]
|
|
169
|
-
for (let i = 0; i < 6; ++i) {
|
|
170
|
-
h.push('<td><table class="colorpanel" cellspacing="0" cellpadding="0">');
|
|
171
|
-
|
|
172
|
-
for (let j = 0, a = cl[i]; j < 6; ++j) {
|
|
173
|
-
h.push('<tr>');
|
|
174
|
-
|
|
175
|
-
for (let k = 0, c = cl[j]; k < 6; ++k) {
|
|
176
|
-
b = cl[k];
|
|
177
|
-
e = (k == 5 && i != 2 && i != 5) ? ';border-right:none;' : '';
|
|
178
|
-
f = (j == 5 && i < 3) ? ';border-bottom:none' : '';
|
|
179
|
-
d = '#' + a + b + c;
|
|
180
|
-
// T = document.all ? ' ' : '';
|
|
181
|
-
h.push('<td unselectable="on" style="background-color: ' + d + e + f + '" title="' + d + '"></td>');
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
h.push('</tr>');
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
h.push('</table></td>');
|
|
188
|
-
|
|
189
|
-
if (cl[i] == '66')
|
|
190
|
-
h.push('</tr><tr>');
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
h.push('</tr></table></div>');
|
|
194
|
-
|
|
195
|
-
return h.join('');
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
watch: {
|
|
199
|
-
/**
|
|
200
|
-
* 切換 HTML 編輯 or 可視化編輯
|
|
201
|
-
*
|
|
202
|
-
* @param n
|
|
203
|
-
*/
|
|
204
|
-
isShowCode(n: boolean): void {
|
|
205
|
-
if (n) {
|
|
206
|
-
this.iframeEl.classList.add('hide');
|
|
207
|
-
this.sourceEditor.classList.add('show');
|
|
208
|
-
grayImg.call(this, true);
|
|
209
|
-
} else {
|
|
210
|
-
this.iframeEl.classList.remove('hide');
|
|
211
|
-
this.sourceEditor.classList.remove('show');
|
|
212
|
-
grayImg.call(this, false);
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* 粘贴图片
|
|
220
|
-
*
|
|
221
|
-
* @param this
|
|
222
|
-
* @param ev
|
|
223
|
-
*/
|
|
224
|
-
function onImagePaste(ev: ClipboardEvent): void {
|
|
225
|
-
if (!this.uploadImageActionUrl) {
|
|
226
|
-
alert('未提供图片上传地址');
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
let items: DataTransferItemList | null = ev.clipboardData && ev.clipboardData.items,
|
|
231
|
-
file: File | null = null; // file 就是剪切板中的图片文件
|
|
232
|
-
|
|
233
|
-
if (items && items.length) {// 检索剪切板 items
|
|
234
|
-
for (let i = 0; i < items.length; i++) {
|
|
235
|
-
const item: DataTransferItem = items[i];
|
|
236
|
-
|
|
237
|
-
if (item.type.indexOf('image') !== -1) {
|
|
238
|
-
// @ts-ignore
|
|
239
|
-
if (window.isCreate) { // 有图片
|
|
240
|
-
alert('请保存记录后再上传图片。');
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
file = item.getAsFile();
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (file) {
|
|
251
|
-
// ev.preventDefault();
|
|
252
|
-
// img.changeBlobImageQuality(file, (newBlob: Blob): void => {
|
|
253
|
-
// // 复用上传的方法
|
|
254
|
-
// Vue.options.components["aj-xhr-upload"].extendOptions.methods.doUpload.call({
|
|
255
|
-
// action: this.uploadImageActionUrl,
|
|
256
|
-
// progress: 0,
|
|
257
|
-
// uploadOk_callback(j: ImgUploadRepsonseResult) {
|
|
258
|
-
// if (j.isOk)
|
|
259
|
-
// this.format("insertImage", this.ajResources.imgPerfix + j.imgUrl);
|
|
260
|
-
// },
|
|
261
|
-
// $blob: newBlob,
|
|
262
|
-
// $fileName: 'foo.jpg' // 文件名不重要,反正上传到云空间会重命名
|
|
263
|
-
// });
|
|
264
|
-
// });
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* 一键存图
|
|
270
|
-
*
|
|
271
|
-
* @param this
|
|
272
|
-
*/
|
|
273
|
-
function saveRemoteImage2Local(): void {
|
|
274
|
-
const arr: NodeListOf<HTMLImageElement> = this.iframeDoc.querySelectorAll('img'),
|
|
275
|
-
remotePicArr: HTMLImageElement[] = new Array<HTMLImageElement>(),
|
|
276
|
-
srcs: string[] = [];
|
|
277
|
-
|
|
278
|
-
for (let i = 0, j = arr.length; i < j; i++) {
|
|
279
|
-
const imgEl: HTMLImageElement = arr[i],
|
|
280
|
-
src: string = <string>imgEl.getAttribute('src');
|
|
281
|
-
|
|
282
|
-
if (/^http/.test(src)) {
|
|
283
|
-
remotePicArr.push(imgEl);
|
|
284
|
-
srcs.push(src);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (srcs.length) {
|
|
289
|
-
// xhr.post('../downAllPics/', (json: any) => {
|
|
290
|
-
// const _arr: string[] = json.pics;
|
|
291
|
-
|
|
292
|
-
// for (let i = 0, j = _arr.length; i < j; i++)
|
|
293
|
-
// remotePicArr[i].src = "images/" + _arr[i]; // 改变 DOM 的旧图片地址为新的
|
|
294
|
-
|
|
295
|
-
// alert('所有图片下载完成。');
|
|
296
|
-
// }, { pics: srcs.join('|') });
|
|
297
|
-
} else
|
|
298
|
-
alert('未发现有远程图片');
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/**
|
|
302
|
-
* 使图标变灰色
|
|
303
|
-
*
|
|
304
|
-
* @param this
|
|
305
|
-
* @param isGray
|
|
306
|
-
*/
|
|
307
|
-
function grayImg(isGray: boolean): void {
|
|
308
|
-
this.$el.querySelectorAll('.toolbar i').forEach((item: HTMLElement) => {
|
|
309
|
-
if (item.className.indexOf('switchMode') != -1) // 这个按钮永远可按,不受影响
|
|
310
|
-
return;
|
|
311
|
-
item.style.color = isGray ? 'lightgray' : '';
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Remove additional MS Word content
|
|
317
|
-
* MSWordHtmlCleaners.js https://gist.github.com/ronanguilloux/2915995
|
|
318
|
-
*
|
|
319
|
-
* @param html
|
|
320
|
-
*/
|
|
321
|
-
function cleanPaste(html: string): string {
|
|
322
|
-
html = html.replace(/<(\/)*(\\?xml:|meta|link|span|font|del|ins|st1:|[ovwxp]:)((.|\s)*?)>/gi, ''); // Unwanted tags
|
|
323
|
-
html = html.replace(/(class|style|type|start)=("(.*?)"|(\w*))/gi, ''); // Unwanted sttributes
|
|
324
|
-
html = html.replace(/<style(.*?)style>/gi, ''); // Style tags
|
|
325
|
-
html = html.replace(/<script(.*?)script>/gi, ''); // Script tags
|
|
326
|
-
html = html.replace(/<!--(.*?)-->/gi, ''); // HTML comments
|
|
327
|
-
|
|
328
|
-
return html;
|
|
329
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="aj-form-html-editor">
|
|
3
|
-
<ul class="toolbar">
|
|
4
|
-
<li class="dorpdown">
|
|
5
|
-
<i title="字体" class="fa-font"></i>
|
|
6
|
-
<div class="fontfamilyChoser" @click="onFontfamilyChoserClk">
|
|
7
|
-
<a style="font-family: '宋体'">宋体</a>
|
|
8
|
-
<a style="font-family: '黑体'">黑体</a>
|
|
9
|
-
<a style="font-family: '楷体'">楷体</a>
|
|
10
|
-
<a style="font-family: '隶书'">隶书</a>
|
|
11
|
-
<a style="font-family: '幼圆'">幼圆</a>
|
|
12
|
-
<a style="font-family: 'Microsoft YaHei'">Microsoft YaHei</a>
|
|
13
|
-
<a style="font-family: Arial">Arial</a>
|
|
14
|
-
<a style="font-family: 'Arial Narrow'">Arial Narrow</a>
|
|
15
|
-
<a style="font-family: 'Arial Black'">Arial Black</a>
|
|
16
|
-
<a style="font-family: 'Comic Sans MS'">Comic Sans MS</a>
|
|
17
|
-
<a style="font-family: Courier">Courier</a>
|
|
18
|
-
<a style="font-family: System">System</a>
|
|
19
|
-
<a style="font-family: 'Times New Roman'">Times New Roman</a>
|
|
20
|
-
<a style="font-family: Verdana">Verdana</a>
|
|
21
|
-
</div>
|
|
22
|
-
</li>
|
|
23
|
-
<li class="dorpdown">
|
|
24
|
-
<i title="字号" class="fa-header"></i>
|
|
25
|
-
<div class="fontsizeChoser" @click="onFontsizeChoserClk">
|
|
26
|
-
<a style="font-size: xx-small; ">极小</a>
|
|
27
|
-
<a style="font-size: x-small; ">特小</a>
|
|
28
|
-
<a style="font-size: small; ">小</a>
|
|
29
|
-
<a style="font-size: medium; ">中</a>
|
|
30
|
-
<a style="font-size: large; ">大</a>
|
|
31
|
-
<a style="font-size: x-large; ">特大</a>
|
|
32
|
-
<a style="font-size: xx-large; line-height: 140%">极大</a>
|
|
33
|
-
</div>
|
|
34
|
-
</li>
|
|
35
|
-
<li @click="onCmdClk"><i title="加粗" class="bold fa-bold"></i></li>
|
|
36
|
-
<li @click="onCmdClk"><i title="斜体" class="italic fa-italic"></i></li>
|
|
37
|
-
<li @click="onCmdClk"><i title="下划线" class="underline fa-underline"></i></li>
|
|
38
|
-
<li @click="onCmdClk"><i title="左对齐" class="justifyleft fa-align-left"></i></li>
|
|
39
|
-
<li @click="onCmdClk"><i title="中间对齐" class="justifycenter fa-align-center"></i></li>
|
|
40
|
-
<li @click="onCmdClk"><i title="右对齐" class="justifyright fa-align-right"></i></li>
|
|
41
|
-
<li @click="onCmdClk"><i title="数字编号" class="insertorderedlist fa-list-ol"></i></li>
|
|
42
|
-
<li @click="onCmdClk"><i title="项目编号" class="insertunorderedlist fa-list-ul"></i></li>
|
|
43
|
-
<li @click="onCmdClk"><i title="增加缩进" class="outdent fa-outdent"></i></li>
|
|
44
|
-
<li @click="onCmdClk"><i title="减少缩进" class="indent fa-indent"></i></li>
|
|
45
|
-
<li class="dorpdown">
|
|
46
|
-
<i title="字体颜色" class="fa-paint-brush"></i>
|
|
47
|
-
<div class="colorPicker" v-html="createColorPickerHTML()" @click="format('foreColor', $event.target.title)"></div>
|
|
48
|
-
</li>
|
|
49
|
-
<li class="dorpdown">
|
|
50
|
-
<i title="背景颜色" class="fa-pencil"></i>
|
|
51
|
-
<div class="colorPicker" v-html="createColorPickerHTML()" @click="format('backColor', $event.target.title)"></div>
|
|
52
|
-
</li>
|
|
53
|
-
<li @click="createLink"><i title="增加链接" class="fa-link"></i></li>
|
|
54
|
-
<li @click="insertImage"><i title="增加图片" class="fa-file-image-o"></i></li>
|
|
55
|
-
<li @click="saveRemoteImage2Local"><i title="一键存图" class="saveRemoteImage2Local fa-hdd-o"></i></li>
|
|
56
|
-
<li @click="cleanHTML"><i title="清理 HTML" class="fa-eraser"></i></li>
|
|
57
|
-
<li @click="isShowCode = !isShowCode"><i title="切换到代码" class="switchMode fa-code"></i></li>
|
|
58
|
-
</ul>
|
|
59
|
-
|
|
60
|
-
<div class="editorBody">
|
|
61
|
-
<iframe srcdoc="<html><body></body></html>"></iframe>
|
|
62
|
-
<textarea></textarea>
|
|
63
|
-
</div>
|
|
64
|
-
<!-- <aj-form-popup-upload ref="uploadLayer" :upload-url="uploadImageActionUrl"></aj-form-popup-upload> -->
|
|
65
|
-
</div>
|
|
66
|
-
</template>
|
|
67
|
-
|
|
68
|
-
<script lang="ts" src="./HtmlEditor.ts"></script>
|
|
69
|
-
|
|
70
|
-
<style lang="less" src="./HtmlEditor.less"></style>
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
// https://github.com/jitbit/HtmlSanitizer/blob/master/HtmlSanitizer.js
|
|
2
|
-
// @ts-ignore
|
|
3
|
-
HtmlSanitizer = new (function foo() {
|
|
4
|
-
var tagWhitelist_ = {
|
|
5
|
-
'A': true, 'ABBR': true, 'B': true, 'BLOCKQUOTE': true, 'BODY': true, 'BR': true, 'CENTER': true, 'CODE': true, 'DIV': true, 'EM': true, 'FONT': true,
|
|
6
|
-
'H1': true, 'H2': true, 'H3': true, 'H4': true, 'H5': true, 'H6': true, 'HR': true, 'I': true, 'IMG': true, 'LABEL': true, 'LI': true, 'OL': true, 'P': true, 'PRE': true,
|
|
7
|
-
'SMALL': true, 'SOURCE': true, 'SPAN': true, 'STRONG': true, 'TABLE': true, 'TBODY': true, 'TR': true, 'TD': true, 'TH': true, 'THEAD': true, 'UL': true, 'U': true, 'VIDEO': true
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
var contentTagWhiteList_ = { 'FORM': true }; //tags that will be converted to DIVs
|
|
11
|
-
var attributeWhitelist_ = { 'align': true, 'color': true, 'controls': true, 'height': true, 'href': true, 'src': true, 'style': false, 'target': true, 'title': true, 'type': true, 'width': true };
|
|
12
|
-
var cssWhitelist_ = { 'color': true, 'background-color': true, 'font-size': true, 'text-align': true, 'text-decoration': true, 'font-weight': true };
|
|
13
|
-
var schemaWhiteList_ = ['http:', 'https:', 'data:', 'm-files:', 'file:', 'ftp:']; //which "protocols" are allowed in "href", "src" etc
|
|
14
|
-
var uriAttributes_ = { 'href': true, 'action': true };
|
|
15
|
-
// @ts-ignore
|
|
16
|
-
this.SanitizeHtml = function (input) {
|
|
17
|
-
input = input.trim();
|
|
18
|
-
if (input == "") return ""; //to save performance and not create iframe
|
|
19
|
-
|
|
20
|
-
//firefox "bogus node" workaround
|
|
21
|
-
if (input == "<br>") return "";
|
|
22
|
-
|
|
23
|
-
var iframe = document.createElement('iframe');
|
|
24
|
-
if (iframe['sandbox'] === undefined) {
|
|
25
|
-
alert('Your browser does not support sandboxed iframes. Please upgrade to a modern browser.');
|
|
26
|
-
return '';
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
iframe['sandbox'] = 'allow-same-origin';
|
|
30
|
-
iframe.style.display = 'none';
|
|
31
|
-
document.body.appendChild(iframe); // necessary so the iframe contains a document
|
|
32
|
-
|
|
33
|
-
var iframedoc = iframe.contentDocument || iframe.contentWindow.document;
|
|
34
|
-
if (iframedoc.body == null) iframedoc.write("<body></body>"); // null in IE
|
|
35
|
-
iframedoc.body.innerHTML = input;
|
|
36
|
-
|
|
37
|
-
function makeSanitizedCopy(node) {
|
|
38
|
-
if (node.nodeType == Node.TEXT_NODE) {
|
|
39
|
-
var newNode = node.cloneNode(true);
|
|
40
|
-
} else if (node.nodeType == Node.ELEMENT_NODE && (tagWhitelist_[node.tagName] || contentTagWhiteList_[node.tagName])) {
|
|
41
|
-
|
|
42
|
-
//remove useless empty spans (lots of those when pasting from MS Outlook)
|
|
43
|
-
if ((node.tagName == "SPAN" || node.tagName == "B" || node.tagName == "I" || node.tagName == "U")
|
|
44
|
-
&& node.innerHTML.trim() == "") {
|
|
45
|
-
return document.createDocumentFragment();
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (contentTagWhiteList_[node.tagName])
|
|
49
|
-
newNode = iframedoc.createElement('DIV'); //convert to DIV
|
|
50
|
-
else
|
|
51
|
-
newNode = iframedoc.createElement(node.tagName);
|
|
52
|
-
|
|
53
|
-
for (var i = 0; i < node.attributes.length; i++) {
|
|
54
|
-
var attr = node.attributes[i];
|
|
55
|
-
if (attributeWhitelist_[attr.name]) {
|
|
56
|
-
if (attr.name == "style") {
|
|
57
|
-
for (s = 0; s < node.style.length; s++) {
|
|
58
|
-
var styleName = node.style[s];
|
|
59
|
-
if (cssWhitelist_[styleName])
|
|
60
|
-
newNode.style.setProperty(styleName, node.style.getPropertyValue(styleName));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
if (uriAttributes_[attr.name]) { //if this is a "uri" attribute, that can have "javascript:" or something
|
|
65
|
-
if (attr.value.indexOf(":") > -1 && !startsWithAny(attr.value, schemaWhiteList_))
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
newNode.setAttribute(attr.name, attr.value);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
for (i = 0; i < node.childNodes.length; i++) {
|
|
73
|
-
var subCopy = makeSanitizedCopy(node.childNodes[i]);
|
|
74
|
-
newNode.appendChild(subCopy, false);
|
|
75
|
-
}
|
|
76
|
-
} else {
|
|
77
|
-
newNode = document.createDocumentFragment();
|
|
78
|
-
}
|
|
79
|
-
return newNode;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
var resultElement = makeSanitizedCopy(iframedoc.body);
|
|
83
|
-
document.body.removeChild(iframe);
|
|
84
|
-
return resultElement.innerHTML
|
|
85
|
-
.replace(/<br[^>]*>(\S)/g, "<br>\n$1")
|
|
86
|
-
.replace(/div><div/g, "div>\n<div"); //replace is just for cleaner code
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function startsWithAny(str, substrings) {
|
|
90
|
-
for (var i = 0; i < substrings.length; i++) {
|
|
91
|
-
if (str.indexOf(substrings[i]) == 0) {
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
this.AllowedTags = tagWhitelist_;
|
|
100
|
-
this.AllowedAttributes = attributeWhitelist_;
|
|
101
|
-
this.AllowedCssStyles = cssWhitelist_;
|
|
102
|
-
this.AllowedSchemas = schemaWhiteList_;
|
|
103
|
-
});
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<select :name="fieldName" class="aj-select" @change="onSelected" style="min-width:180px;"></select>
|
|
3
|
-
</template>
|
|
4
|
-
|
|
5
|
-
<script lang="ts">
|
|
6
|
-
// interface TreeOption {
|
|
7
|
-
// /**
|
|
8
|
-
// * 是否创建全部
|
|
9
|
-
// */
|
|
10
|
-
// makeAllOption?: boolean;
|
|
11
|
-
|
|
12
|
-
// /**
|
|
13
|
-
// * 默认为"所有分类", 或者你在这里自定义一个名字
|
|
14
|
-
// */
|
|
15
|
-
// allOptionName?: string;
|
|
16
|
-
// }
|
|
17
|
-
|
|
18
|
-
// /**
|
|
19
|
-
// * 下拉分类选择器,异步请求远端获取分类数据
|
|
20
|
-
// */
|
|
21
|
-
// export default {
|
|
22
|
-
// props: {
|
|
23
|
-
// fieldName: { type: String, required: false, default: "catalogId" }, // 表单 name,字段名
|
|
24
|
-
// apiUrl: {
|
|
25
|
-
// type: String,
|
|
26
|
-
// default() {
|
|
27
|
-
// return ctx + "/admin/tree-like/";
|
|
28
|
-
// },
|
|
29
|
-
// },
|
|
30
|
-
// isAutoLoad: { type: Boolean, default: true },
|
|
31
|
-
// isAutoJump: Boolean,
|
|
32
|
-
// initFieldValue: String,
|
|
33
|
-
// },
|
|
34
|
-
|
|
35
|
-
// data() {
|
|
36
|
-
// return {
|
|
37
|
-
// fieldValue: this.initFieldValue,
|
|
38
|
-
// };
|
|
39
|
-
// },
|
|
40
|
-
// mounted(): void {
|
|
41
|
-
// this.isAutoLoad && this.getData();
|
|
42
|
-
// },
|
|
43
|
-
// methods: {
|
|
44
|
-
// onSelected(ev: Event): void {
|
|
45
|
-
// let el: HTMLSelectElement = <HTMLSelectElement>ev.target;
|
|
46
|
-
// this.fieldValue = el.selectedOptions[0].value;
|
|
47
|
-
|
|
48
|
-
// if (this.isAutoJump)
|
|
49
|
-
// location.assign("?" + this.fieldName + "=" + this.fieldValue);
|
|
50
|
-
// else
|
|
51
|
-
// this.BUS && this.BUS.$emit("aj-tree-catelog-select-change", ev, this);
|
|
52
|
-
// },
|
|
53
|
-
|
|
54
|
-
// getData(): void {
|
|
55
|
-
// let fn = (j: RepsonseResult) => {
|
|
56
|
-
// let arr = [{ id: 0, name: "请选择分类" }];
|
|
57
|
-
// rendererOption(
|
|
58
|
-
// <TreeNode[]>arr.concat(j.result),
|
|
59
|
-
// <HTMLSelectElement>this.$el,
|
|
60
|
-
// this.fieldValue,
|
|
61
|
-
// { makeAllOption: false }
|
|
62
|
-
// );
|
|
63
|
-
|
|
64
|
-
// if (this.fieldValue)
|
|
65
|
-
// // 有指定的选中值
|
|
66
|
-
// //@ts-ignore
|
|
67
|
-
// form.utils.selectOption.call(this, this.fieldValue);
|
|
68
|
-
// };
|
|
69
|
-
|
|
70
|
-
// // aj.xhr.get(this.ajResources.ctx + this.apiUrl + "/admin/tree-like/getListAndSubByParentId/", fn);
|
|
71
|
-
// xhr.get(this.apiUrl, fn);
|
|
72
|
-
// },
|
|
73
|
-
// },
|
|
74
|
-
// };
|
|
75
|
-
|
|
76
|
-
// /**
|
|
77
|
-
// * 渲染 Option 标签的 DOM
|
|
78
|
-
// *
|
|
79
|
-
// * @param jsonArray
|
|
80
|
-
// * @param select
|
|
81
|
-
// * @param selectedId
|
|
82
|
-
// * @param cfg
|
|
83
|
-
// */
|
|
84
|
-
// export function rendererOption(
|
|
85
|
-
// jsonArray: TreeNode[],
|
|
86
|
-
// select: HTMLSelectElement,
|
|
87
|
-
// selectedId?: string,
|
|
88
|
-
// cfg?: TreeOption
|
|
89
|
-
// ): void {
|
|
90
|
-
// if (cfg && cfg.makeAllOption) {
|
|
91
|
-
// let option: HTMLOptionElement = document.createElement("option");
|
|
92
|
-
// option.innerHTML = cfg.allOptionName || "全部分类";
|
|
93
|
-
// select.appendChild(option);
|
|
94
|
-
// }
|
|
95
|
-
|
|
96
|
-
// let treeNode: TreeNode | null = toTreeMap(jsonArray);
|
|
97
|
-
// if (treeNode) {
|
|
98
|
-
// // 生成 option
|
|
99
|
-
// let temp: DocumentFragment = document.createDocumentFragment();
|
|
100
|
-
// console.log(treeNode);
|
|
101
|
-
|
|
102
|
-
// output(<TreeNode>treeNode, (node: TreeNode, nodeId: string) => {
|
|
103
|
-
// let option: HTMLOptionElement = document.createElement("option"); // 节点
|
|
104
|
-
// option.value = nodeId;
|
|
105
|
-
|
|
106
|
-
// if (selectedId && selectedId == nodeId)
|
|
107
|
-
// // 选中的
|
|
108
|
-
// option.selected = true;
|
|
109
|
-
|
|
110
|
-
// option.dataset["pid"] = node.pid + "";
|
|
111
|
-
// //option.style= "padding-left:" + (node.level - 1) +"rem;";
|
|
112
|
-
// option.innerHTML =
|
|
113
|
-
// new Array(node.level * 5).join(" ") +
|
|
114
|
-
// (node.level == 1 ? "" : "└─") +
|
|
115
|
-
// node.name;
|
|
116
|
-
// temp.appendChild(option);
|
|
117
|
-
// });
|
|
118
|
-
|
|
119
|
-
// select.appendChild(temp);
|
|
120
|
-
// }
|
|
121
|
-
// }
|
|
122
|
-
</script>
|
|
123
|
-
|
|
124
|
-
<style lang="less" scoped>
|
|
125
|
-
</style>
|