@ajaxjs/util 1.0.9 → 1.1.1
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/LICENSE +201 -0
- package/README.md +11 -20
- package/dist/index.d.ts +34 -6
- package/dist/index.js +45 -8
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +1 -0
- package/dist/main.js +16 -0
- package/dist/main.js.map +1 -0
- package/dist/router/index.d.ts +3 -0
- package/dist/router/index.js +44 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router.d.ts +3 -0
- package/dist/router.js +44 -0
- package/dist/router.js.map +1 -0
- package/dist/shims-vue.d.ts +4 -0
- package/dist/style/common-functions.less +294 -0
- package/dist/style/reset.less +19 -0
- package/dist/util/cookies.d.ts +18 -0
- package/dist/util/cookies.js +46 -0
- package/dist/util/cookies.js.map +1 -0
- package/dist/util/dom.d.ts +17 -0
- package/dist/util/dom.js +44 -0
- package/dist/util/dom.js.map +1 -0
- package/dist/util/utils.d.ts +51 -0
- package/dist/util/utils.js +174 -0
- package/dist/util/utils.js.map +1 -0
- package/dist/{xhr-config.js.map → util/xhr-config.js.map} +1 -1
- package/dist/{xhr.d.ts → util/xhr.d.ts} +16 -1
- package/dist/{xhr.js → util/xhr.js} +84 -36
- package/dist/util/xhr.js.map +1 -0
- package/dist/widget/AccordionMenu.vue +140 -0
- package/dist/widget/AdjustFontSize.vue +65 -0
- package/dist/widget/Article.vue +59 -0
- package/dist/widget/EmptyContent.d.ts +5 -0
- package/dist/widget/EmptyContent.js +7 -0
- package/dist/widget/EmptyContent.js.map +1 -0
- package/dist/widget/Expander.vue +65 -0
- package/dist/widget/FileUploader/FileUploader.d.ts +70 -0
- package/dist/widget/FileUploader/FileUploader.js +139 -0
- package/dist/widget/FileUploader/FileUploader.js.map +1 -0
- package/dist/widget/FileUploader/FileUploader.less +68 -0
- package/dist/widget/FileUploader/FileUploader.ts +156 -0
- package/dist/widget/FileUploader/FileUploader.vue +43 -0
- package/dist/widget/HtmlEditor/HtmlEditor.d.ts +70 -0
- package/dist/widget/HtmlEditor/HtmlEditor.js +287 -0
- package/dist/widget/HtmlEditor/HtmlEditor.js.map +1 -0
- package/dist/widget/HtmlEditor/HtmlEditor.less +345 -0
- package/dist/widget/HtmlEditor/HtmlEditor.ts +339 -0
- package/dist/widget/HtmlEditor/HtmlEditor.vue +70 -0
- package/dist/widget/HtmlEditor/html-editor-HtmlSanitizer.js +103 -0
- package/dist/widget/ImageEnlarger.vue +105 -0
- package/dist/widget/OpacityBanner.vue +125 -0
- package/dist/widget/ProcessLine.vue +133 -0
- package/dist/widget/Resize.d.ts +51 -0
- package/dist/widget/Resize.js +133 -0
- package/dist/widget/Resize.js.map +1 -0
- package/dist/widget/Resize.ts +152 -0
- package/dist/widget/Resize.vue +104 -0
- package/dist/widget/TreeSelector.vue +4 -0
- package/dist/widget/calendar/BetweenDate.vue +63 -0
- package/dist/widget/calendar/Calendar.d.ts +55 -0
- package/dist/widget/calendar/Calendar.js +145 -0
- package/dist/widget/calendar/Calendar.js.map +1 -0
- package/dist/widget/calendar/Calendar.less +210 -0
- package/dist/widget/calendar/Calendar.ts +167 -0
- package/dist/widget/calendar/Calendar.vue +52 -0
- package/dist/widget/calendar/CalendarInput.vue +71 -0
- package/dist/widget/form/validator.d.ts +70 -0
- package/dist/widget/form/validator.js +220 -0
- package/dist/widget/form/validator.js.map +1 -0
- package/dist/widget/form/validator.ts +289 -0
- package/dist/widget/play-ground/sku.vue +93 -0
- package/package.json +31 -15
- package/dist/base.d.ts +0 -42
- package/dist/base.js +0 -133
- package/dist/base.js.map +0 -1
- package/dist/entity.d.ts +0 -26
- package/dist/entity.js +0 -2
- package/dist/entity.js.map +0 -1
- package/dist/xhr.js.map +0 -1
- package/src/base.ts +0 -145
- package/src/entity.ts +0 -31
- package/src/index.ts +0 -13
- package/src/xhr-config.ts +0 -25
- package/src/xhr.ts +0 -233
- package/tsconfig.json +0 -73
- /package/dist/{xhr-config.d.ts → util/xhr-config.d.ts} +0 -0
- /package/dist/{xhr-config.js → util/xhr-config.js} +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
props: {
|
|
3
|
+
vModel: String, // 双向绑定
|
|
4
|
+
uploadImageActionUrl: String, // 图片上传路径
|
|
5
|
+
isIonicons: Boolean // 是否使用 ionicons 图标
|
|
6
|
+
},
|
|
7
|
+
data(): any {
|
|
8
|
+
return {
|
|
9
|
+
isShowCode: false, // 是否显示 HTML 源码
|
|
10
|
+
iframeEl: null,
|
|
11
|
+
sourceEditor: null,
|
|
12
|
+
};
|
|
13
|
+
},
|
|
14
|
+
mounted(): void {
|
|
15
|
+
this.iframeEl = <HTMLIFrameElement>this.$el.querySelector('iframe');
|
|
16
|
+
this.sourceEditor = <HTMLTextAreaElement>this.$el.querySelector('textarea');
|
|
17
|
+
|
|
18
|
+
(<Window>this.iframeEl.contentWindow).onload = (ev: Event) => { // 这个方法只能写在 onload 事件里面, 不写 onload 里还不执行
|
|
19
|
+
this.iframeDoc = (<Window>this.iframeEl.contentWindow).document;
|
|
20
|
+
this.iframeDoc.designMode = 'on';
|
|
21
|
+
this.iframeDoc.addEventListener('paste', onImagePaste.bind(this));// 直接剪切板粘贴上传图片
|
|
22
|
+
|
|
23
|
+
new MutationObserver((mutationsList: MutationRecord[], observer: MutationObserver) => {// 监听DOM树变化
|
|
24
|
+
if (!this.isShowCode) {
|
|
25
|
+
this.sourceEditor.value = this.iframeDoc.body.innerHTML;
|
|
26
|
+
this.$emit('on-change', this.sourceEditor.value)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
}).observe(this.iframeDoc.body, { attributes: true, childList: true, subtree: true, characterData: true });
|
|
30
|
+
|
|
31
|
+
this.vModel && this.setIframeBody(this.vModel);
|
|
32
|
+
// this.sourceEditor.value && this.setIframeBody(this.sourceEditor.value);// 有内容
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.sourceEditor.oninput = (ev: Event) => {
|
|
36
|
+
if (this.isShowCode && this.sourceEditor.value) {
|
|
37
|
+
this.setIframeBody(this.sourceEditor.value);
|
|
38
|
+
this.$emit('on-change', this.sourceEditor.value)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// this.uploadImgMgr = this.$refs.uploadLayer;
|
|
43
|
+
},
|
|
44
|
+
methods: {
|
|
45
|
+
/**
|
|
46
|
+
* 输入 HTML 内容
|
|
47
|
+
*
|
|
48
|
+
* @param html
|
|
49
|
+
*/
|
|
50
|
+
setIframeBody(html: string): void {
|
|
51
|
+
if (this.iframeDoc && this.iframeDoc.body)
|
|
52
|
+
this.iframeDoc.body.innerHTML = html;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 获取内容的 HTML
|
|
57
|
+
*
|
|
58
|
+
* @param cleanWord 是否清理冗余标签
|
|
59
|
+
* @param encode 是否 URL 编码
|
|
60
|
+
*/
|
|
61
|
+
getValue(cleanWord: boolean, encode: boolean): string {
|
|
62
|
+
let result: string = this.iframeDoc.body.innerHTML;
|
|
63
|
+
|
|
64
|
+
if (cleanWord)
|
|
65
|
+
result = cleanPaste(result);
|
|
66
|
+
|
|
67
|
+
if (encode)
|
|
68
|
+
result = encodeURIComponent(result);
|
|
69
|
+
|
|
70
|
+
return result;
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
createLink(): void {
|
|
74
|
+
let result: string = prompt("请输入 URL 地址");
|
|
75
|
+
if (result)
|
|
76
|
+
this.format("createLink", result);
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
insertImage(): void {
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
if (window.isCreate)
|
|
82
|
+
alert('请保存记录后再上传图片。');
|
|
83
|
+
else {
|
|
84
|
+
this.uploadImgMgr.show((json: any) => {
|
|
85
|
+
if (json && json.isOk)
|
|
86
|
+
this.format("insertImage", json.fullUrl);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 清理冗余 HTML
|
|
93
|
+
*/
|
|
94
|
+
cleanHTML(): void {
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
this.setIframeBody(HtmlSanitizer.SanitizeHtml(this.iframeDoc.body.innerHTML));
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
saveRemoteImage2Local(): void {
|
|
100
|
+
saveRemoteImage2Local.call(this);
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 当工具条点击的时候触发
|
|
105
|
+
*
|
|
106
|
+
* @param ev
|
|
107
|
+
*/
|
|
108
|
+
onCmdClk(ev: Event): void {
|
|
109
|
+
let el: HTMLElement = <HTMLElement>ev.target,
|
|
110
|
+
clsName = <string>el.className.split(' ').shift();
|
|
111
|
+
|
|
112
|
+
this.format(clsName);
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 通过 document.execCommand() 来操纵可编辑内容区域的元素
|
|
117
|
+
*
|
|
118
|
+
* @param type 命令的名称
|
|
119
|
+
* @param para 一些命令(例如 insertImage)需要额外的参数(insertImage 需要提供插入 image 的 url),默认为 null
|
|
120
|
+
*/
|
|
121
|
+
format(type: string, para?: string): void {
|
|
122
|
+
if (para)
|
|
123
|
+
this.iframeDoc.execCommand(type, false, para);
|
|
124
|
+
else
|
|
125
|
+
this.iframeDoc.execCommand(type, false);
|
|
126
|
+
|
|
127
|
+
// (<Window>this.iframeEl.contentWindow).focus();
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 选择字号大小
|
|
132
|
+
*
|
|
133
|
+
* @param ev
|
|
134
|
+
*/
|
|
135
|
+
onFontsizeChoserClk(ev: Event): void {
|
|
136
|
+
let el: HTMLElement = <HTMLElement>ev.target,
|
|
137
|
+
els = (<HTMLElement>ev.currentTarget).children;
|
|
138
|
+
|
|
139
|
+
let i: number, j: number;
|
|
140
|
+
for (i = 0, j = els.length; i < j; i++)
|
|
141
|
+
if (el == els[i])
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
this.format('fontsize', i + "");
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 选择字体
|
|
149
|
+
*
|
|
150
|
+
* @param ev
|
|
151
|
+
*/
|
|
152
|
+
onFontfamilyChoserClk(ev: Event): void {
|
|
153
|
+
let el: HTMLElement = <HTMLElement>ev.target;
|
|
154
|
+
this.format('fontname', el.innerHTML);
|
|
155
|
+
|
|
156
|
+
/* 如何解决点击之后马上隐藏面板?由于 js(单击事件) 没有控制 CSS 的 :hover 伪类的方法,故所以必须使用以下技巧:*/
|
|
157
|
+
let menuPanel: HTMLElement = <HTMLElement>el.parentNode;
|
|
158
|
+
menuPanel.style.display = 'none';
|
|
159
|
+
setTimeout(() => menuPanel.style.display = '', 300);
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 创建颜色选择器
|
|
164
|
+
*/
|
|
165
|
+
createColorPickerHTML(): string {
|
|
166
|
+
let cl: string[] = ['00', '33', '66', '99', 'CC', 'FF'],
|
|
167
|
+
b: string, d: string, e: string, f: string,
|
|
168
|
+
h: string[] = ['<div class="colorhead"><span class="colortitle">颜色选择</span></div><div class="colorbody"><table cellspaci="0" cellpadding="0"><tr>'];
|
|
169
|
+
|
|
170
|
+
// 创建 body [6 x 6的色盘]
|
|
171
|
+
for (let i = 0; i < 6; ++i) {
|
|
172
|
+
h.push('<td><table class="colorpanel" cellspacing="0" cellpadding="0">');
|
|
173
|
+
|
|
174
|
+
for (let j = 0, a = cl[i]; j < 6; ++j) {
|
|
175
|
+
h.push('<tr>');
|
|
176
|
+
|
|
177
|
+
for (let k = 0, c = cl[j]; k < 6; ++k) {
|
|
178
|
+
b = cl[k];
|
|
179
|
+
e = (k == 5 && i != 2 && i != 5) ? ';border-right:none;' : '';
|
|
180
|
+
f = (j == 5 && i < 3) ? ';border-bottom:none' : '';
|
|
181
|
+
d = '#' + a + b + c;
|
|
182
|
+
// T = document.all ? ' ' : '';
|
|
183
|
+
h.push('<td unselectable="on" style="background-color: ' + d + e + f + '" title="' + d + '"></td>');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
h.push('</tr>');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
h.push('</table></td>');
|
|
190
|
+
|
|
191
|
+
if (cl[i] == '66')
|
|
192
|
+
h.push('</tr><tr>');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
h.push('</tr></table></div>');
|
|
196
|
+
|
|
197
|
+
return h.join('');
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
watch: {
|
|
202
|
+
vModel(newHtml: string, oldHtml: string): void {
|
|
203
|
+
// if (!html)
|
|
204
|
+
// html = '';
|
|
205
|
+
if (!oldHtml) // 当没有值的时候输入,就是在初始化的时候(第一次)
|
|
206
|
+
this.setIframeBody(newHtml);
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 切換 HTML 編輯 or 可視化編輯
|
|
211
|
+
*
|
|
212
|
+
* @param n
|
|
213
|
+
*/
|
|
214
|
+
isShowCode(n: boolean): void {
|
|
215
|
+
if (n) {
|
|
216
|
+
this.iframeEl.classList.add('hide');
|
|
217
|
+
this.sourceEditor.classList.add('show');
|
|
218
|
+
grayImg.call(this, true);
|
|
219
|
+
} else {
|
|
220
|
+
this.iframeEl.classList.remove('hide');
|
|
221
|
+
this.sourceEditor.classList.remove('show');
|
|
222
|
+
grayImg.call(this, false);
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 粘贴图片
|
|
230
|
+
*
|
|
231
|
+
* @param this
|
|
232
|
+
* @param ev
|
|
233
|
+
*/
|
|
234
|
+
function onImagePaste(ev: ClipboardEvent): void {
|
|
235
|
+
if (!this.uploadImageActionUrl) {
|
|
236
|
+
alert('未提供图片上传地址');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let items: DataTransferItemList | null = ev.clipboardData && ev.clipboardData.items,
|
|
241
|
+
file: File | null = null; // file 就是剪切板中的图片文件
|
|
242
|
+
|
|
243
|
+
if (items && items.length) {// 检索剪切板 items
|
|
244
|
+
for (let i = 0; i < items.length; i++) {
|
|
245
|
+
const item: DataTransferItem = items[i];
|
|
246
|
+
|
|
247
|
+
if (item.type.indexOf('image') !== -1) {
|
|
248
|
+
// @ts-ignore
|
|
249
|
+
if (window.isCreate) { // 有图片
|
|
250
|
+
alert('请保存记录后再上传图片。');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
file = item.getAsFile();
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (file) {
|
|
261
|
+
// ev.preventDefault();
|
|
262
|
+
// img.changeBlobImageQuality(file, (newBlob: Blob): void => {
|
|
263
|
+
// // 复用上传的方法
|
|
264
|
+
// Vue.options.components["aj-xhr-upload"].extendOptions.methods.doUpload.call({
|
|
265
|
+
// action: this.uploadImageActionUrl,
|
|
266
|
+
// progress: 0,
|
|
267
|
+
// uploadOk_callback(j: ImgUploadRepsonseResult) {
|
|
268
|
+
// if (j.isOk)
|
|
269
|
+
// this.format("insertImage", this.ajResources.imgPerfix + j.imgUrl);
|
|
270
|
+
// },
|
|
271
|
+
// $blob: newBlob,
|
|
272
|
+
// $fileName: 'foo.jpg' // 文件名不重要,反正上传到云空间会重命名
|
|
273
|
+
// });
|
|
274
|
+
// });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 一键存图
|
|
280
|
+
*
|
|
281
|
+
* @param this
|
|
282
|
+
*/
|
|
283
|
+
function saveRemoteImage2Local(): void {
|
|
284
|
+
const arr: NodeListOf<HTMLImageElement> = this.iframeDoc.querySelectorAll('img'),
|
|
285
|
+
remotePicArr: HTMLImageElement[] = new Array<HTMLImageElement>(),
|
|
286
|
+
srcs: string[] = [];
|
|
287
|
+
|
|
288
|
+
for (let i = 0, j = arr.length; i < j; i++) {
|
|
289
|
+
const imgEl: HTMLImageElement = arr[i],
|
|
290
|
+
src: string = <string>imgEl.getAttribute('src');
|
|
291
|
+
|
|
292
|
+
if (/^http/.test(src)) {
|
|
293
|
+
remotePicArr.push(imgEl);
|
|
294
|
+
srcs.push(src);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (srcs.length) {
|
|
299
|
+
// xhr.post('../downAllPics/', (json: any) => {
|
|
300
|
+
// const _arr: string[] = json.pics;
|
|
301
|
+
|
|
302
|
+
// for (let i = 0, j = _arr.length; i < j; i++)
|
|
303
|
+
// remotePicArr[i].src = "images/" + _arr[i]; // 改变 DOM 的旧图片地址为新的
|
|
304
|
+
|
|
305
|
+
// alert('所有图片下载完成。');
|
|
306
|
+
// }, { pics: srcs.join('|') });
|
|
307
|
+
} else
|
|
308
|
+
alert('未发现有远程图片');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* 使图标变灰色
|
|
313
|
+
*
|
|
314
|
+
* @param this
|
|
315
|
+
* @param isGray
|
|
316
|
+
*/
|
|
317
|
+
function grayImg(isGray: boolean): void {
|
|
318
|
+
this.$el.querySelectorAll('.toolbar i').forEach((item: HTMLElement) => {
|
|
319
|
+
if (item.className.indexOf('switchMode') != -1) // 这个按钮永远可按,不受影响
|
|
320
|
+
return;
|
|
321
|
+
item.style.color = isGray ? 'lightgray' : '';
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Remove additional MS Word content
|
|
327
|
+
* MSWordHtmlCleaners.js https://gist.github.com/ronanguilloux/2915995
|
|
328
|
+
*
|
|
329
|
+
* @param html
|
|
330
|
+
*/
|
|
331
|
+
function cleanPaste(html: string): string {
|
|
332
|
+
html = html.replace(/<(\/)*(\\?xml:|meta|link|span|font|del|ins|st1:|[ovwxp]:)((.|\s)*?)>/gi, ''); // Unwanted tags
|
|
333
|
+
html = html.replace(/(class|style|type|start)=("(.*?)"|(\w*))/gi, ''); // Unwanted sttributes
|
|
334
|
+
html = html.replace(/<style(.*?)style>/gi, ''); // Style tags
|
|
335
|
+
html = html.replace(/<script(.*?)script>/gi, ''); // Script tags
|
|
336
|
+
html = html.replace(/<!--(.*?)-->/gi, ''); // HTML comments
|
|
337
|
+
|
|
338
|
+
return html;
|
|
339
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="aj-form-html-editor">
|
|
3
|
+
<ul class="toolbar">
|
|
4
|
+
<li class="dorpdown">
|
|
5
|
+
<i title="字体" class="text-icon">A</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="text-icon">H</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 text-icon">B</i></li>
|
|
36
|
+
<li @click="onCmdClk"><i title="斜体" class="italic text-icon" style="font-style:italic">I</i></li>
|
|
37
|
+
<li @click="onCmdClk"><i title="下划线" class="underline text-icon" style="text-decoration: underline;">U</i></li>
|
|
38
|
+
<li @click="onCmdClk"><i title="左对齐" :class="'justifyleft '+ (isIonicons ? 'ivu-icon ivu-icon-bingo-menu-fold' : 'fontAwesome fa-align-left')"></i></li>
|
|
39
|
+
<li @click="onCmdClk"><i title="中间对齐" :class="'justifycenter '+ (isIonicons ? 'ivu-icon ivu-icon-md-menu' : 'fontAwesome fa-align-center')"></i></li>
|
|
40
|
+
<li @click="onCmdClk"><i title="右对齐" :class="'justifyright '+ (isIonicons ? 'ivu-icon ivu-icon-bingo-menu-unfold' : 'fontAwesome fa-align-right')"></i></li>
|
|
41
|
+
<li @click="onCmdClk"><i title="数字编号" :class="'insertorderedlist '+ (isIonicons ? 'ivu-icon ivu-icon-md-list' : 'fontAwesome fa-list-ol')"></i></li>
|
|
42
|
+
<li @click="onCmdClk"><i title="项目编号" :class="'insertunorderedlist '+ (isIonicons ? 'ivu-icon ivu-icon-ios-list' : 'fontAwesome fa-list-ul')"></i></li>
|
|
43
|
+
<li @click="onCmdClk"><i title="增加缩进" :class="'outdent '+ (isIonicons ? 'ivu-icon ivu-icon-ios-return-left' : 'fontAwesome fa-outdent')"></i></li>
|
|
44
|
+
<li @click="onCmdClk"><i title="减少缩进" :class="'indent '+ (isIonicons ? 'ivu-icon ivu-icon-ios-return-right' : 'fontAwesome fa-indent')"></i></li>
|
|
45
|
+
<li class="dorpdown">
|
|
46
|
+
<i title="字体颜色" :class="isIonicons ? 'ivu-icon ivu-icon-md-brush' : 'fontAwesome 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="isIonicons ? 'ivu-icon ivu-icon-ios-brush-outline' : 'fontAwesome 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="isIonicons ? 'ivu-icon ivu-icon-ios-link' : 'fontAwesome fa-link'"></i></li>
|
|
54
|
+
<li @click="insertImage"><i title="增加图片" :class="isIonicons ? 'ivu-icon ivu-icon-md-images' : 'fontAwesome fa-regular fa-image'"></i></li>
|
|
55
|
+
<li @click="saveRemoteImage2Local"><i title="一键存图" :class="isIonicons ? 'ivu-icon ivu-icon-md-photos' : 'fontAwesome fa-camera'"></i></li>
|
|
56
|
+
<li @click="cleanHTML"><i title="清理 HTML" :class="isIonicons ? 'ivu-icon ivu-icon-md-done-all' : 'fontAwesome fa-eraser'"></i></li>
|
|
57
|
+
<li @click="isShowCode = !isShowCode"><i title="切换到代码" :class="(isIonicons ? 'ivu-icon ivu-icon-md-code' : 'fontAwesome fa-code')+ ' switchMode'"></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>
|
|
@@ -0,0 +1,103 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<a class="aj-img-thumb" :href="imgUrl" v-if="imgUrl" target="_blank">
|
|
3
|
+
<img :src="imgUrl" @mouseenter="current = imgUrl" @mouseleave="current = null" />
|
|
4
|
+
</a>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script>
|
|
8
|
+
/**
|
|
9
|
+
* 悬浮显示大图
|
|
10
|
+
*/
|
|
11
|
+
export default {
|
|
12
|
+
props: {
|
|
13
|
+
imgUrl: { type: String, required: true },// 图片地址
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
data() {
|
|
17
|
+
return { current: '' }
|
|
18
|
+
},
|
|
19
|
+
watch: {
|
|
20
|
+
current(v) {
|
|
21
|
+
let el = document.querySelector("body > div.aj-image-large-view");
|
|
22
|
+
|
|
23
|
+
if (v) {
|
|
24
|
+
el.querySelector('img').src = v;
|
|
25
|
+
setTimeout(() => el.style.display = 'block', 700);
|
|
26
|
+
} else
|
|
27
|
+
el.style.display = 'none';
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
mounted() {
|
|
31
|
+
// 初始化
|
|
32
|
+
let div = document.querySelector("body > div.aj-image-large-view");
|
|
33
|
+
|
|
34
|
+
if (!div) {
|
|
35
|
+
div = document.createElement("div");
|
|
36
|
+
div.className = "aj-image-large-view";
|
|
37
|
+
div.innerHTML = "<img />";
|
|
38
|
+
|
|
39
|
+
document.body.appendChild(div);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.$el.addEventListener("mousemove", throttle(e => {
|
|
43
|
+
if (this.imgUrl) {
|
|
44
|
+
let w = 0, imgWidth = div.querySelector("img").clientWidth;
|
|
45
|
+
|
|
46
|
+
if (imgWidth > e.pageX)
|
|
47
|
+
w = imgWidth;
|
|
48
|
+
|
|
49
|
+
div.style.top = e.pageY + 20 + "px";
|
|
50
|
+
div.style.left = e.pageX - div.clientWidth + w + "px";
|
|
51
|
+
}
|
|
52
|
+
}, 50, 5000), false);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 函数节流
|
|
58
|
+
*
|
|
59
|
+
* @author https://www.cnblogs.com/moqiutao/p/6875955.html
|
|
60
|
+
* @param fn
|
|
61
|
+
* @param delay
|
|
62
|
+
* @param mustRunDelay
|
|
63
|
+
*/
|
|
64
|
+
function throttle(fn, delay, mustRunDelay) {
|
|
65
|
+
var timer, t_start;
|
|
66
|
+
return function () {
|
|
67
|
+
var _this = this;
|
|
68
|
+
var t_curr = +new Date();
|
|
69
|
+
window.clearTimeout(timer);
|
|
70
|
+
|
|
71
|
+
if (!t_start)
|
|
72
|
+
t_start = t_curr;
|
|
73
|
+
|
|
74
|
+
if (t_curr - t_start >= mustRunDelay) {
|
|
75
|
+
// @ts-ignore
|
|
76
|
+
fn.apply(this, arguments);
|
|
77
|
+
t_start = t_curr;
|
|
78
|
+
} else {
|
|
79
|
+
var args = arguments;
|
|
80
|
+
// @ts-ignore
|
|
81
|
+
timer = window.setTimeout(function () {
|
|
82
|
+
return fn.apply(_this, args);
|
|
83
|
+
}, delay);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<style lang="less">
|
|
90
|
+
.aj-img-thumb img {
|
|
91
|
+
max-width: 50px;
|
|
92
|
+
max-height: 60px;
|
|
93
|
+
vertical-align: middle;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.aj-image-large-view {
|
|
97
|
+
position: fixed;
|
|
98
|
+
max-width: 400px;
|
|
99
|
+
transition: top ease-in 200ms, left ease-in 200ms;
|
|
100
|
+
|
|
101
|
+
img {
|
|
102
|
+
width: 100%;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
</style>
|