1 |
|
2 | import { isObject, isString } from './utils';
|
3 | import { computedImageSize } from './helpers';
|
4 | import { addEvent } from './helpers';
|
5 | import EMOJIMAP from './emoji.json';
|
6 |
|
7 |
|
8 |
|
9 |
|
10 | let isDev = false;
|
11 |
|
12 |
|
13 |
|
14 |
|
15 | let isSSR = false;
|
16 |
|
17 | try {
|
18 | isDev = process.env && process.env.NODE_ENV === 'development';
|
19 |
|
20 | isSSR = process.env && process.env.VUE_ENV === 'server';
|
21 | } catch (e) {
|
22 |
|
23 | }
|
24 |
|
25 |
|
26 |
|
27 |
|
28 | const validImageRegexp = /^(http|https):\/\/(imgtest|img)\.guihuazhi\.com\/[A-z0-9/.]*?$/;
|
29 |
|
30 |
|
31 |
|
32 |
|
33 | const validEmoji = /(\[[\u4e00-\u9fa5]{1,4}\])/g;
|
34 |
|
35 | class Parse {
|
36 | constructor(option) {
|
37 | this.platform = option.platform;
|
38 | }
|
39 |
|
40 | createElement(d) {
|
41 | if (isObject(d)) {
|
42 | const { tag, attrs = {}, child = [], props, content } = d;
|
43 | let node = document.createElement(tag);
|
44 |
|
45 | Object.keys(attrs).forEach(function(key) {
|
46 | node.setAttribute(key, attrs[key]);
|
47 | });
|
48 |
|
49 | if (tag === 'img') {
|
50 | node = this.renderImageDom(props, attrs);
|
51 | }
|
52 |
|
53 | if (tag === 'a') {
|
54 | node = this.renderLinkDom(props);
|
55 | }
|
56 |
|
57 | if (content) {
|
58 | node.innerHTML = content;
|
59 | } else {
|
60 | child.forEach(c => {
|
61 | node.appendChild(this.createElement(c));
|
62 | });
|
63 | }
|
64 |
|
65 | return node;
|
66 | } else if (typeof d === 'string') {
|
67 | return document.createTextNode(d);
|
68 | }
|
69 | }
|
70 |
|
71 | reset() {
|
72 | this.unbindImageEvents && this.unbindImageEvents();
|
73 | }
|
74 |
|
75 |
|
76 | bindImageLazyLoad() {
|
77 | this.clientHeight = window.screen.height;
|
78 |
|
79 | this.unbindImageEvents = addEvent(
|
80 | window,
|
81 | 'scroll',
|
82 | this.loadImageFunc.bind(this)
|
83 | );
|
84 | setTimeout(() => {
|
85 |
|
86 | this.loadImageFunc();
|
87 | }, 0);
|
88 | }
|
89 |
|
90 |
|
91 | unbindImageLazyLoad() {
|
92 | this.unbindImageEvents();
|
93 | }
|
94 |
|
95 | loadImageFunc() {
|
96 | (this.imageDoc =
|
97 | this.imageDoc ||
|
98 | document.querySelectorAll(`.huazhi.huazhi-${this.platform} img`)).forEach(
|
99 | el => {
|
100 | if (el.getAttribute('src')) return;
|
101 |
|
102 | const { src } = el.dataset;
|
103 |
|
104 | const o = el.getBoundingClientRect();
|
105 |
|
106 | if (o.top < this.clientHeight) {
|
107 | el.setAttribute('src', src);
|
108 | el.previousSibling.style.opacity = 0;
|
109 | }
|
110 | }
|
111 | );
|
112 | }
|
113 |
|
114 | |
115 |
|
116 |
|
117 |
|
118 | renderDomTreeHTML(tree, platform) {
|
119 | return tree
|
120 | .map(block => {
|
121 | switch (block.tag) {
|
122 | case 'h2':
|
123 | return `<h2>${this.renderDomTreeHTML(block.child)}</h2>`;
|
124 |
|
125 | case 'blockquote':
|
126 | return `<blockquote>${this.renderDomTreeHTML(
|
127 | block.child
|
128 | )}</blockquote>`;
|
129 |
|
130 | case 'p':
|
131 | return `<p>${this.renderDomTreeHTML(block.child)}</p>`;
|
132 |
|
133 | case 'b':
|
134 | return `<b>${block.content}</b>`;
|
135 |
|
136 | case 'i':
|
137 | return `<i style="background-image: url(${
|
138 | block.props.url
|
139 | })" emoji="${block.props.emoji}"></i>`;
|
140 |
|
141 | case 'img':
|
142 | return this.renderImage(block.props, block.attrs);
|
143 |
|
144 | case 'video':
|
145 | return this.renderVideo(block.props);
|
146 |
|
147 | case 'hr':
|
148 | return '<hr />';
|
149 |
|
150 | case 'a':
|
151 | return this.renderLink(block.props);
|
152 |
|
153 |
|
154 | default:
|
155 |
|
156 | return isString(block) ? block : '';
|
157 | }
|
158 | })
|
159 | .join('');
|
160 | }
|
161 |
|
162 |
|
163 |
|
164 |
|
165 |
|
166 | |
167 |
|
168 |
|
169 |
|
170 |
|
171 | jsonToDomTree(json) {
|
172 | let results = [];
|
173 | let imageIndex = 0;
|
174 |
|
175 | if (typeof json === 'string') {
|
176 | try {
|
177 | json = JSON.parse(json);
|
178 | } catch (e) {
|
179 |
|
180 | return results;
|
181 | }
|
182 | }
|
183 |
|
184 | for (let i = 0, l = json.length; i < l; i++) {
|
185 | let block = json[i];
|
186 | if (block.type === 'text') {
|
187 | let tag;
|
188 | if (block.head === 1) {
|
189 | tag = 'h2';
|
190 | } else if (block.block === 1) {
|
191 | tag = 'blockquote';
|
192 | } else {
|
193 | tag = 'p';
|
194 | }
|
195 | let child = this.parseInline(block.value);
|
196 | results.push({ tag, child });
|
197 | } else if (block.type === 'image' && this.isImage(block)) {
|
198 | results.push({
|
199 | tag: 'img',
|
200 | attrs: {
|
201 | src: block.url,
|
202 | index: imageIndex++
|
203 | },
|
204 | props: block
|
205 | });
|
206 | } else if (block.type === 'line') {
|
207 | results.push({
|
208 | tag: 'hr'
|
209 | });
|
210 | } else if (block.type === 'video') {
|
211 | results.push({
|
212 | tag: 'video',
|
213 | props: block
|
214 | });
|
215 | }
|
216 | }
|
217 |
|
218 | return results;
|
219 | }
|
220 |
|
221 | |
222 |
|
223 |
|
224 |
|
225 |
|
226 | parseComment(str) {
|
227 | let result = this.parseEmoji(str);
|
228 | let html = this.renderDomTreeHTML(result, this.platform);
|
229 | return html;
|
230 | }
|
231 |
|
232 | |
233 |
|
234 |
|
235 |
|
236 |
|
237 |
|
238 |
|
239 |
|
240 | jsonToHTML(json, wrapperDom, containerWidth = 0) {
|
241 |
|
242 | if (typeof json === 'string') {
|
243 | try {
|
244 | json = JSON.parse(json);
|
245 | } catch (e) {
|
246 | if (isDev) throw new Error('json type error');
|
247 | return '';
|
248 | }
|
249 | }
|
250 |
|
251 | const tree = this.jsonToDomTree(json);
|
252 |
|
253 |
|
254 |
|
255 | let html = this.renderDomTreeHTML(tree, this.platform, containerWidth);
|
256 |
|
257 | if (isSSR || this.platform === 'editor') {
|
258 | return html;
|
259 | }
|
260 |
|
261 | if (wrapperDom) {
|
262 | wrapperDom.classList.add('huazhi', `huazhi-${this.platform}`);
|
263 | wrapperDom.innerHTML = html;
|
264 | }
|
265 |
|
266 |
|
267 | if (this.lazyLoad) {
|
268 |
|
269 | this.bindImageLazyLoad();
|
270 | }
|
271 |
|
272 | if (this.initEvents && typeof this.initEvents === 'function') {
|
273 | this.initEvents();
|
274 | }
|
275 |
|
276 | return wrapperDom ? wrapperDom : html;
|
277 | }
|
278 |
|
279 | renderText(value) {
|
280 | let html = '';
|
281 | let length = value.length;
|
282 | for (let i = 0; i < length; i++) {
|
283 | let v = value[i];
|
284 | if (v.bold) {
|
285 | html += `<strong>${v.content}</strong>`
|
286 | } else if (v.type === 'link') {
|
287 | html += `${this.renderLink(v)}`
|
288 | } else {
|
289 | html += `${v.content}`
|
290 | }
|
291 | }
|
292 | if (length === 0) {
|
293 | html = '<br>'
|
294 | }
|
295 | return html;
|
296 | }
|
297 |
|
298 | renderBlock(o) {
|
299 | let classStr = o.center ? "class='ql-align-center'" : '';
|
300 | if (o.head) {
|
301 | return `<h2 ${classStr}>${this.renderText(o.value)}</h2>`
|
302 | } else if (o.block) {
|
303 | return `<blockquote ${classStr}>${this.renderText(o.value)}</blockquote>`
|
304 | } else {
|
305 | return `<p ${classStr}>${this.renderText(o.value)}</p>`
|
306 | }
|
307 | }
|
308 |
|
309 | renderLine() {
|
310 | return "<hr />"
|
311 | }
|
312 |
|
313 | renderList(o) {
|
314 | let html = '';
|
315 | let liList = '';
|
316 | let length = o.value.length;
|
317 | for (let i = 0; i < length; i++) {
|
318 | let v = o.value[i];
|
319 | liList += `<li>${this.renderText(v)}</li>`
|
320 | }
|
321 | if (o.type === 'ol') {
|
322 | html = `<ol>${liList}</ol>`
|
323 | } else if (o.type === 'ul') {
|
324 | html = `<ul>${liList}</ul>`
|
325 | }
|
326 | return html
|
327 | }
|
328 |
|
329 | |
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 | jsonToHTML2(json, wrapperDom, containerWidth = 0) {
|
338 |
|
339 | if (typeof json === 'string') {
|
340 | try {
|
341 | json = JSON.parse(json);
|
342 | } catch (e) {
|
343 | if (isDev) throw new Error('json type error');
|
344 | return '';
|
345 | }
|
346 | }
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 |
|
355 | let html = '';
|
356 | let length = json.length;
|
357 | for (let i = 0; i < length; i++) {
|
358 | let j = json[i];
|
359 | let type = j.type;
|
360 | if (type === 'text') {
|
361 | html +=this.renderBlock(j)
|
362 | } else if (type === 'line') {
|
363 | html +=this.renderLine(j)
|
364 | } else if (type === 'image') {
|
365 | html +=this.renderImage(j)
|
366 | } else if (type === 'video') {
|
367 | html +=this.renderVideo(j)
|
368 | } else if (type === 'ul' || type === 'ol') {
|
369 | html +=this.renderList(j)
|
370 | } else {
|
371 | if (j.content) {
|
372 | html += `<p>${json[i].content}</p>`
|
373 | }
|
374 | }
|
375 | }
|
376 |
|
377 | if (this.initEvents && typeof this.initEvents === 'function') {
|
378 |
|
379 | this.initEvents();
|
380 | }
|
381 |
|
382 |
|
383 | return html;
|
384 |
|
385 | }
|
386 |
|
387 | |
388 |
|
389 |
|
390 |
|
391 |
|
392 | parseEmoji(text) {
|
393 | if (typeof text !== 'string') return '';
|
394 | let results = [];
|
395 | let splitIndex = 0;
|
396 | let tmp;
|
397 |
|
398 | while ((tmp = validEmoji.exec(text))) {
|
399 | let { 0: emojiName, index: startIndex } = tmp;
|
400 | let { lastIndex } = validEmoji;
|
401 |
|
402 | if (EMOJIMAP[emojiName]) {
|
403 | results.push(text.substring(splitIndex, startIndex));
|
404 |
|
405 | results.push({
|
406 | tag: 'i',
|
407 | props: {
|
408 | emoji: emojiName,
|
409 | url: EMOJIMAP[emojiName].url
|
410 | },
|
411 | attr: {}
|
412 | });
|
413 |
|
414 |
|
415 | splitIndex = lastIndex;
|
416 | }
|
417 | }
|
418 |
|
419 |
|
420 | if (splitIndex < text.length) {
|
421 | results.push(text.substring(splitIndex));
|
422 | }
|
423 |
|
424 | return results;
|
425 | }
|
426 |
|
427 | |
428 |
|
429 |
|
430 |
|
431 |
|
432 | parseInline(inlineList) {
|
433 | let results = [];
|
434 | for (let j = 0, l = inlineList.length; j < l; j++) {
|
435 | let inline = inlineList[j];
|
436 | if (inline.type === 'string') {
|
437 | if (this.platform === 'editor') {
|
438 |
|
439 | if (j === inlineList.length - 1) {
|
440 | if (inline.content.endsWith('\n') || inline.content.endsWith('\r\n')) {
|
441 | inline.content = inline.content.slice(0, -1)
|
442 | }
|
443 | }
|
444 | }
|
445 | if (inline.bold === 1) {
|
446 |
|
447 | results.push({ tag: 'b', attr: {}, content: inline.content });
|
448 | } else {
|
449 |
|
450 | results.push(...this.parseEmoji(inline.content));
|
451 | }
|
452 | } else if (inline.type === 'link') {
|
453 | results.push({
|
454 | tag: 'a',
|
455 | props: {
|
456 | ...inline,
|
457 | }
|
458 | });
|
459 | }
|
460 | }
|
461 |
|
462 | return results;
|
463 | }
|
464 |
|
465 | |
466 |
|
467 |
|
468 |
|
469 |
|
470 | isImage(image) {
|
471 | return (
|
472 | isObject(image) &&
|
473 | typeof image.url === 'string' &&
|
474 | validImageRegexp.test(image.url)
|
475 | );
|
476 | }
|
477 |
|
478 | |
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 | htmlToJSON(source, isOps, isJSonString = true) {
|
487 | if (isUndefined(source) || (!isString(source) && !isArray(source))) {
|
488 | if (isDev)
|
489 | throw new Error('source must be a string type or quill detail');
|
490 |
|
491 | return isJSonString ? '[]' : [];
|
492 | }
|
493 |
|
494 | let results = [];
|
495 |
|
496 | return results;
|
497 | }
|
498 | }
|
499 |
|
500 | export default Parse;
|