Dotclear

source: plugins/dcLegacyEditor/js/jsToolBar/jsToolBar.wysiwyg.js @ 3915:a57821ba9ef1

Revision 3915:a57821ba9ef1, 27.4 KB checked in by franck <carnet.franck.paul@…>, 7 years ago (diff)

Switching from inline JS variables to JSON script. A step ahead…

Line 
1/*global jsToolBar, dotclear, chainHandler */
2'use strict';
3
4/* ***** BEGIN LICENSE BLOCK *****
5 * This file is part of DotClear.
6 * Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All
7 * rights reserved.
8 *
9 * DotClear is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * DotClear is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with DotClear; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 *
23 * ***** END LICENSE BLOCK *****
24 */
25
26jsToolBar.prototype.can_wwg = (document.designMode != undefined);
27jsToolBar.prototype.iframe = null;
28jsToolBar.prototype.iwin = null;
29jsToolBar.prototype.ibody = null;
30jsToolBar.prototype.iframe_css = null;
31
32/* Editor methods
33-------------------------------------------------------- */
34jsToolBar.prototype.drawToolBar = jsToolBar.prototype.draw;
35jsToolBar.prototype.draw = function(mode) {
36  mode = mode || 'xhtml';
37
38  if (this.can_wwg) {
39    this.mode = 'wysiwyg';
40    this.drawToolBar('wysiwyg');
41    this.initWindow();
42  } else {
43    this.drawToolBar(mode);
44  }
45};
46
47jsToolBar.prototype.switchMode = function(mode) {
48  mode = mode || 'xhtml';
49
50  if (mode == 'xhtml') {
51    this.wwg_mode = true;
52    this.draw(mode);
53  } else {
54    if (this.wwg_mode) {
55      this.syncContents('iframe');
56    }
57    this.wwg_mode = false;
58    this.removeEditor();
59    this.textarea.style.display = '';
60    this.drawToolBar(mode);
61  }
62};
63
64jsToolBar.prototype.syncContents = function(from) {
65  from = from || 'textarea';
66  const This = this;
67  if (from == 'textarea') {
68    initContent();
69  } else {
70    this.validBlockquote();
71    let html = this.applyHtmlFilters(this.ibody.innerHTML);
72    if (html == '<br />') {
73      html = '<p></p>';
74    }
75    this.textarea.value = html;
76  }
77
78  function initContent() {
79    if (!This.iframe.contentWindow.document || !This.iframe.contentWindow.document.body) {
80      setTimeout(initContent, 1);
81      return;
82    }
83    This.ibody = This.iframe.contentWindow.document.body;
84
85    if (This.textarea.value != '' && This.textarea.value != '<p></p>') {
86      This.ibody.innerHTML = This.applyWysiwygFilters(This.textarea.value);
87      if (This.ibody.createTextRange) { //cursor at the begin for IE
88        const IErange = This.ibody.createTextRange();
89        IErange.execCommand("SelectAll");
90        IErange.collapse();
91        IErange.select();
92      }
93    } else {
94      const idoc = This.iwin.document;
95      const para = idoc.createElement('p');
96      para.appendChild(idoc.createElement('br'));
97      while (idoc.body.hasChildNodes()) {
98        idoc.body.removeChild(idoc.body.lastChild);
99      }
100      idoc.body.appendChild(para);
101    }
102  }
103};
104jsToolBar.prototype.htmlFilters = {
105  tagsoup: function(str) {
106    return this.tagsoup2xhtml(str);
107  }
108};
109jsToolBar.prototype.applyHtmlFilters = function(str) {
110  for (let fn in this.htmlFilters) {
111    str = this.htmlFilters[fn].call(this, str);
112  }
113  return str;
114};
115jsToolBar.prototype.wysiwygFilters = {};
116jsToolBar.prototype.applyWysiwygFilters = function(str) {
117  for (let fn in this.wysiwygFilters) {
118    str = this.wysiwygFilters[fn].call(this, str);
119  }
120  return str;
121};
122
123jsToolBar.prototype.switchEdit = function() {
124  if (this.wwg_mode) {
125    this.textarea.style.display = '';
126    this.iframe.style.display = 'none';
127    this.syncContents('iframe');
128    this.drawToolBar('xhtml');
129    this.wwg_mode = false;
130    this.focusEditor();
131  } else {
132    this.iframe.style.display = '';
133    this.textarea.style.display = 'none';
134    this.syncContents('textarea');
135    this.drawToolBar('wysiwyg');
136    this.wwg_mode = true;
137    this.focusEditor();
138  }
139  this.setSwitcher();
140};
141
142/** Creates iframe for editor, inits a blank document
143 */
144jsToolBar.prototype.initWindow = function() {
145  const This = this;
146
147  this.iframe = document.createElement('iframe');
148  this.textarea.parentNode.insertBefore(this.iframe, this.textarea.nextSibling);
149
150  this.switcher = document.createElement('ul');
151  this.switcher.className = 'jstSwitcher';
152  this.editor.appendChild(this.switcher);
153
154  this.iframe.height = this.textarea.offsetHeight + 0;
155  this.iframe.width = this.textarea.offsetWidth + 0;
156
157  if (this.textarea.tabIndex != undefined) {
158    this.iframe.tabIndex = this.textarea.tabIndex;
159  }
160
161  function initIframe() {
162    const doc = This.iframe.contentWindow.document;
163    if (!doc) {
164      setTimeout(initIframe, 1);
165      return false;
166    }
167
168    doc.open();
169    const html =
170`<html>
171  <head>
172    <link rel="stylesheet" href="style/default.css" type="text/css" media="screen" />
173    <style type="text/css">${This.iframe_css}</style>
174    ${This.base_url != '' ? `<base href="${This.base_url}" />` : ''}
175  </head>
176  <body></body>
177</html>`;
178
179    doc.write(html);
180    doc.close();
181    if (document.all) { // for IE
182      doc.designMode = 'on';
183      // warning : doc is now inaccessible for IE6 sp1
184    }
185
186    if (dotclear && dotclear.data && dotclear.data.htmlFontSize) {
187      doc.documentElement.style.setProperty('--html-font-size', dotclear.data.htmlFontSize);
188    }
189
190    // Set lang if set for the textarea
191    if (This.textarea.lang) {
192      doc.documentElement.setAttribute('lang', This.textarea.lang);
193    }
194
195    This.iwin = This.iframe.contentWindow;
196
197    This.syncContents('textarea');
198
199    if (This.wwg_mode == undefined) {
200      This.wwg_mode = true;
201    }
202
203    if (This.wwg_mode) {
204      This.textarea.style.display = 'none';
205    } else {
206      This.iframe.style.display = 'none';
207    }
208
209    // update textarea on submit
210    if (This.textarea.form) {
211      chainHandler(This.textarea.form, 'onsubmit', function() {
212        if (This.wwg_mode) {
213          This.syncContents('iframe');
214        }
215      });
216    }
217
218    for (let evt in This.iwinEvents) {
219      const event = This.iwinEvents[evt];
220      This.addIwinEvent(This.iframe.contentWindow.document, event.type, event.fn, This);
221    }
222
223    This.setSwitcher();
224    try {
225      This.iwin.document.designMode = 'on';
226    } catch (e) {} // Firefox needs this
227
228    return true;
229  }
230  initIframe();
231};
232jsToolBar.prototype.addIwinEvent = function(target, type, fn, scope) {
233  const myFn = function(e) {
234    fn.call(scope, e);
235  };
236  addEvent(target, type, myFn, true);
237  // fix memory leak
238  addEvent(scope.iwin, 'unload', function() {
239    removeEvent(target, type, myFn, true);
240  }, true);
241};
242jsToolBar.prototype.iwinEvents = {
243  block1: {
244    type: 'mouseup',
245    fn: function() {
246      this.adjustBlockLevelCombo();
247    }
248  },
249  block2: {
250    type: 'keyup',
251    fn: function() {
252      this.adjustBlockLevelCombo();
253    }
254  }
255};
256
257/** Insert a mode switcher after editor area
258 */
259jsToolBar.prototype.switcher_visual_title = 'visual';
260jsToolBar.prototype.switcher_source_title = 'source';
261jsToolBar.prototype.setSwitcher = function() {
262  while (this.switcher.hasChildNodes()) {
263    this.switcher.removeChild(this.switcher.firstChild);
264  }
265
266  const This = this;
267
268  function setLink(title, link) {
269    const li = document.createElement('li');
270    let a;
271    if (link) {
272      a = document.createElement('a');
273      a.href = '#';
274      a.editor = This;
275      a.onclick = function() {
276        this.editor.switchEdit();
277        return false;
278      };
279      a.appendChild(document.createTextNode(title));
280    } else {
281      li.className = 'jstSwitcherCurrent';
282      a = document.createTextNode(title);
283    }
284
285    li.appendChild(a);
286    This.switcher.appendChild(li);
287  }
288
289  setLink(this.switcher_visual_title, !this.wwg_mode);
290  setLink(this.switcher_source_title, this.wwg_mode);
291};
292
293/** Removes editor area and mode switcher
294 */
295jsToolBar.prototype.removeEditor = function() {
296  if (this.iframe != null) {
297    this.iframe.parentNode.removeChild(this.iframe);
298    this.iframe = null;
299  }
300
301  if (this.switcher != undefined && this.switcher.parentNode != undefined) {
302    this.switcher.parentNode.removeChild(this.switcher);
303  }
304};
305
306/** Focus on the editor area
307 */
308jsToolBar.prototype.focusEditor = function() {
309  if (this.wwg_mode) {
310    try {
311      this.iwin.document.designMode = 'on';
312    } catch (e) {} // Firefox needs this
313    const This = this;
314    setTimeout(function() {
315      This.iframe.contentWindow.focus();
316    }, 1);
317  } else {
318    this.textarea.focus();
319  }
320};
321
322/** Resizer
323 */
324jsToolBar.prototype.resizeSetStartH = function() {
325  if (this.wwg_mode && this.iframe != undefined) {
326    this.dragStartH = this.iframe.offsetHeight;
327    return;
328  }
329  this.dragStartH = this.textarea.offsetHeight + 0;
330};
331jsToolBar.prototype.resizeDragMove = function(event) {
332  const new_height = `${this.dragStartH + event.clientY - this.dragStartY}px`;
333  if (this.iframe != undefined) {
334    this.iframe.style.height = new_height;
335  }
336  this.textarea.style.height = new_height;
337};
338
339/* Editing methods
340-------------------------------------------------------- */
341/** Replaces current selection by given node
342 */
343jsToolBar.prototype.insertNode = function(node) {
344  let range;
345
346  if (this.iwin.getSelection) { // Gecko
347    const sel = this.iwin.getSelection();
348    range = sel.getRangeAt(0);
349
350    // deselect all ranges
351    sel.removeAllRanges();
352
353    // empty range
354    range.deleteContents();
355
356    // Insert node
357    range.insertNode(node);
358
359    range.selectNodeContents(node);
360    range.setEndAfter(node);
361    if (range.endContainer.childNodes.length > range.endOffset &&
362      range.endContainer.nodeType != Node.TEXT_NODE) {
363      range.setEnd(range.endContainer.childNodes[range.endOffset], 0);
364    } else {
365      range.setEnd(range.endContainer.childNodes[0]);
366    }
367    sel.addRange(range);
368
369    sel.collapseToEnd();
370  } else { // IE
371    // lambda element
372    const p = this.iwin.document.createElement('div');
373    p.appendChild(node);
374    range = this.iwin.document.selection.createRange();
375    range.execCommand('delete');
376    // insert innerHTML from element
377    range.pasteHTML(p.innerHTML);
378    range.collapse(false);
379    range.select();
380  }
381  this.iwin.focus();
382};
383
384/** Returns a document fragment with selected nodes
385 */
386jsToolBar.prototype.getSelectedNode = function() {
387  let sel;
388  var content;
389  if (this.iwin.getSelection) { // Gecko
390    sel = this.iwin.getSelection();
391    const range = sel.getRangeAt(0);
392    content = range.cloneContents();
393  } else { // IE
394    sel = this.iwin.document.selection;
395    const d = this.iwin.document.createElement('div');
396    d.innerHTML = sel.createRange().htmlText;
397    content = this.iwin.document.createDocumentFragment();
398    for (let i = 0; i < d.childNodes.length; i++) {
399      content.appendChild(d.childNodes[i].cloneNode(true));
400    }
401  }
402  return content;
403};
404
405/** Returns string representation for selected node
406 */
407jsToolBar.prototype.getSelectedText = function() {
408  if (this.iwin.getSelection) { // Gecko
409    return this.iwin.getSelection().toString();
410  } else { // IE
411    const range = this.iwin.document.selection.createRange();
412    return range.text;
413  }
414};
415
416jsToolBar.prototype.replaceNodeByContent = function(node) {
417  const content = this.iwin.document.createDocumentFragment();
418  for (let i = 0; i < node.childNodes.length; i++) {
419    content.appendChild(node.childNodes[i].cloneNode(true));
420  }
421  node.parentNode.replaceChild(content, node);
422};
423
424jsToolBar.prototype.getBlockLevel = function() {
425  const blockElts = ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
426
427  let range;
428  let commonAncestorContainer;
429  if (this.iwin.getSelection) { //gecko
430    const selection = this.iwin.getSelection();
431    range = selection.getRangeAt(0);
432    commonAncestorContainer = range.commonAncestorContainer;
433    while (commonAncestorContainer.nodeType != 1) {
434      commonAncestorContainer = commonAncestorContainer.parentNode;
435    }
436  } else { //ie
437    range = this.iwin.document.selection.createRange();
438    commonAncestorContainer = range.parentElement();
439  }
440
441  let ancestorTagName = commonAncestorContainer.tagName.toLowerCase();
442  while (arrayIndexOf(blockElts, ancestorTagName) == -1 && ancestorTagName != 'body') {
443    commonAncestorContainer = commonAncestorContainer.parentNode;
444    ancestorTagName = commonAncestorContainer.tagName.toLowerCase();
445  }
446  if (ancestorTagName == 'body') return null;
447  else return commonAncestorContainer;
448};
449jsToolBar.prototype.adjustBlockLevelCombo = function() {
450  const blockLevel = this.getBlockLevel();
451  if (blockLevel !== null)
452    this.toolNodes.blocks.value = blockLevel.tagName.toLowerCase();
453  else {
454    if (this.mode == 'wysiwyg') this.toolNodes.blocks.value = 'none';
455    if (this.mode == 'xhtml') this.toolNodes.blocks.value = 'nonebis';
456  }
457};
458
459/** HTML code cleanup
460-------------------------------------------------------- */
461jsToolBar.prototype.simpleCleanRegex = new Array(
462  /* Remove every tags we don't need */
463  [/<meta[\w\W]*?>/gim, ''], [/<style[\w\W]*?>[\w\W]*?<\/style>/gim, ''], [/<\/?font[\w\W]*?>/gim, ''],
464
465  /* Replacements */
466  [/<(\/?)(B|b|STRONG)([\s>\/])/g, "<$1strong$3"], [/<(\/?)(I|i|EM)([\s>\/])/g, "<$1em$3"], [/<IMG ([^>]*?[^\/])>/gi, "<img $1 />"], [/<INPUT ([^>]*?[^\/])>/gi, "<input $1 />"], [/<COL ([^>]*?[^\/])>/gi, "<col $1 />"], [/<AREA ([^>]*?[^\/])>/gi, "<area $1 />"], [/<PARAM ([^>]*?[^\/])>/gi, "<param $1 />"], [/<HR ([^>]*?[^\/])>/gi, "<hr $1/>"], [/<BR ([^>]*?[^\/])>/gi, "<br $1/>"], [/<(\/?)U([\s>\/])/gi, "<$1ins$2"], [/<(\/?)STRIKE([\s>\/])/gi, "<$1del$2"], [/<span style="font-weight: normal;">([\w\W]*?)<\/span>/gm, "$1"], [/<span style="font-weight: bold;">([\w\W]*?)<\/span>/gm, "<strong>$1</strong>"], [/<span style="font-style: italic;">([\w\W]*?)<\/span>/gm, "<em>$1</em>"], [/<span style="text-decoration: underline;">([\w\W]*?)<\/span>/gm, "<ins>$1</ins>"], [/<span style="text-decoration: line-through;">([\w\W]*?)<\/span>/gm, "<del>$1</del>"], [/<span style="text-decoration: underline line-through;">([\w\W]*?)<\/span>/gm, "<del><ins>$1</ins></del>"], [/<span style="(font-weight: bold; ?|font-style: italic; ?){2}">([\w\W]*?)<\/span>/gm, "<strong><em>$2</em></strong>"], [/<span style="(font-weight: bold; ?|text-decoration: underline; ?){2}">([\w\W]*?)<\/span>/gm, "<ins><strong>$2</strong></ins>"], [/<span style="(font-weight: italic; ?|text-decoration: underline; ?){2}">([\w\W]*?)<\/span>/gm, "<ins><em>$2</em></ins>"], [/<span style="(font-weight: bold; ?|text-decoration: line-through; ?){2}">([\w\W]*?)<\/span>/gm, "<del><strong>$2</strong></del>"], [/<span style="(font-weight: italic; ?|text-decoration: line-through; ?){2}">([\w\W]*?)<\/span>/gm, "<del><em>$2</em></del>"], [/<span style="(font-weight: bold; ?|font-style: italic; ?|text-decoration: underline; ?){3}">([\w\W]*?)<\/span>/gm, "<ins><strong><em>$2</em></strong></ins>"], [/<span style="(font-weight: bold; ?|font-style: italic; ?|text-decoration: line-through; ?){3}">([\w\W]*?)<\/span>/gm, "<del><strong><em>$2</em></strong></del>"], [/<span style="(font-weight: bold; ?|font-style: italic; ?|text-decoration: underline line-through; ?){3}">([\w\W]*?)<\/span>/gm, "<del><ins><strong><em>$2</em></strong></ins></del>"], [/<strong style="font-weight: normal;">([\w\W]*?)<\/strong>/gm, "$1"], [/<([a-z]+) style="font-weight: normal;">([\w\W]*?)<\/\1>/gm, "<$1>$2</$1>"], [/<([a-z]+) style="font-weight: bold;">([\w\W]*?)<\/\1>/gm, "<$1><strong>$2</strong></$1>"], [/<([a-z]+) style="font-style: italic;">([\w\W]*?)<\/\1>/gm, "<$1><em>$2</em></$1>"], [/<([a-z]+) style="text-decoration: underline;">([\w\W]*?)<\/\1>/gm, "<ins><$1>$2</$1></ins>"], [/<([a-z]+) style="text-decoration: line-through;">([\w\W]*?)<\/\1>/gm, "<del><$1>$2</$1></del>"], [/<([a-z]+) style="text-decoration: underline line-through;">([\w\W]*?)<\/\1>/gm, "<del><ins><$1>$2</$1></ins></del>"], [/<([a-z]+) style="(font-weight: bold; ?|font-style: italic; ?){2}">([\w\W]*?)<\/\1>/gm, "<$1><strong><em>$3</em></strong></$1>"], [/<([a-z]+) style="(font-weight: bold; ?|text-decoration: underline; ?){2}">([\w\W]*?)<\/\1>/gm, "<ins><$1><strong>$3</strong></$1></ins>"], [/<([a-z]+) style="(font-weight: italic; ?|text-decoration: underline; ?){2}">([\w\W]*?)<\/\1>/gm, "<ins><$1><em>$3</em></$1></ins>"], [/<([a-z]+) style="(font-weight: bold; ?|text-decoration: line-through; ?){2}">([\w\W]*?)<\/\1>/gm, "<del><$1><strong>$3</strong></$1></del>"], [/<([a-z]+) style="(font-weight: italic; ?|text-decoration: line-through; ?){2}">([\w\W]*?)<\/\1>/gm, "<del><$1><em>$3</em></$1></del>"], [/<([a-z]+) style="(font-weight: bold; ?|font-style: italic; ?|text-decoration: underline; ?){3}">([\w\W]*?)<\/\1>/gm, "<ins><$1><strong><em>$3</em></strong></$1></ins>"], [/<([a-z]+) style="(font-weight: bold; ?|font-style: italic; ?|text-decoration: line-through; ?){3}">([\w\W]*?)<\/\1>/gm, "<del><$1><strong><em>$3</em></strong></$1></del>"], [/<([a-z]+) style="(font-weight: bold; ?|font-style: italic; ?|text-decoration: underline line-through; ?){3}">([\w\W]*?)<\/\1>/gm, "<del><ins><$1><strong><em>$3</em></strong></$1></ins></del>"], [/<p><blockquote>(.*)(\n)+<\/blockquote><\/p>/i, "<blockquote>$1</blockquote>\n"],
467  /* mise en forme identique contigue */
468  [/<\/(strong|em|ins|del|q|code)>(\s*?)<\1>/gim, "$2"], [/<(br|BR)>/g, "<br />"], [/<(hr|HR)>/g, "<hr />"],
469  /* opera est trop strict ;)) */
470  [/([^\s])\/>/g, "$1 />"],
471  /* br intempestifs de fin de block */
472  [/<br \/>\s*<\/(h1|h2|h3|h4|h5|h6|ul|ol|li|p|blockquote|div)/gi, "</$1"], [/<\/(h1|h2|h3|h4|h5|h6|ul|ol|li|p|blockquote)>([^\n\u000B\r\f])/gi, "</$1>\n$2"], [/<hr style="width: 100%; height: 2px;" \/>/g, "<hr />"]
473);
474
475/** Cleanup HTML code
476 */
477jsToolBar.prototype.tagsoup2xhtml = function(html) {
478  for (let reg in this.simpleCleanRegex) {
479    html = html.replace(this.simpleCleanRegex[reg][0], this.simpleCleanRegex[reg][1]);
480  }
481  /* tags vides */
482  /* note : on tente de ne pas tenir compte des commentaires html, ceux-ci
483     permettent entre autre d'inserer des commentaires conditionnels pour ie */
484  while (/(<[^\/!]>|<[^\/!][^>]*[^\/]>)\s*<\/[^>]*[^-]>/.test(html)) {
485    html = html.replace(/(<[^\/!]>|<[^\/!][^>]*[^\/]>)\s*<\/[^>]*[^-]>/g, "");
486  }
487
488  /* tous les tags en minuscule */
489  html = html.replace(/<(\/?)([A-Z0-9]+)/g,
490    function(match0, match1, match2) {
491      return "<" + match1 + match2.toLowerCase();
492    });
493
494  /* IE laisse souvent des attributs sans guillemets */
495  const myRegexp = /<[^>]+((\s+\w+\s*=\s*)([^"'][\w~@+$,%\/:.#?=&;!*()-]*))[^>]*?>/;
496  const myQuoteFn = function(str, val1, val2, val3) {
497    const tamponRegex = new RegExp(regexpEscape(val1));
498    return str.replace(tamponRegex, val2 + '"' + val3 + '"');
499  };
500  while (myRegexp.test(html)) {
501    html = html.replace(myRegexp, myQuoteFn);
502  }
503
504  /* les navigateurs rajoutent une unite aux longueurs css nulles */
505  /* note: a ameliorer ! */
506  while (/(<[^>]+style=(["'])[^>]+[\s:]+)0(pt|px)(\2|\s|;)/.test(html)) {
507    html = html.replace(/(<[^>]+style=(["'])[^>]+[\s:]+)0(pt|px)(\2|\s|;)/gi, "$1" + "0$4");
508  }
509
510  /* correction des fins de lignes : le textarea edite contient des \n
511   * le wysiwyg des \r\n , et le textarea mis a jour SANS etre affiche des \r\n ! */
512  html = html.replace(/\r\n/g, "\n");
513
514  /* Trim only if there's no pre tag */
515  const pattern_pre = /<pre>[\s\S]*<\/pre>/gi;
516  if (!pattern_pre.test(html)) {
517    html = html.replace(/^\s+/gm, '');
518    html = html.replace(/\s+$/gm, '');
519  }
520
521  return html;
522};
523jsToolBar.prototype.validBlockquote = function() {
524  const blockElts = ['address', 'blockquote', 'dl', 'div', 'fieldset', 'form', 'h1',
525    'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'ol', 'p', 'pre', 'table', 'ul'
526  ];
527  const BQs = this.iwin.document.getElementsByTagName('blockquote');
528  let bqChilds;
529  let p;
530
531  for (let bq = 0; bq < BQs.length; bq++) {
532    bqChilds = BQs[bq].childNodes;
533    let frag = this.iwin.document.createDocumentFragment();
534    for (let i = (bqChilds.length - 1); i >= 0; i--) {
535      if (bqChilds[i].nodeType == 1 && // Node.ELEMENT_NODE
536        arrayIndexOf(blockElts, bqChilds[i].tagName.toLowerCase()) >= 0) {
537        if (frag.childNodes.length > 0) {
538          p = this.iwin.document.createElement('p');
539          p.appendChild(frag);
540          BQs[bq].replaceChild(p, bqChilds[i + 1]);
541          frag = this.iwin.document.createDocumentFragment();
542        }
543      } else {
544        if (frag.childNodes.length > 0) BQs[bq].removeChild(bqChilds[i + 1]);
545        frag.insertBefore(bqChilds[i].cloneNode(true), frag.firstChild);
546      }
547    }
548    if (frag.childNodes.length > 0) {
549      p = this.iwin.document.createElement('p');
550      p.appendChild(frag);
551      BQs[bq].replaceChild(p, bqChilds[0]);
552    }
553  }
554};
555
556/* Removing text formating */
557jsToolBar.prototype.removeFormatRegexp = new Array(
558  [/(<[a-z][^>]*)margin\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)margin-bottom\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)margin-left\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)margin-right\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)margin-top\s*:[^;]*;/mg, "$1"],
559
560  [/(<[a-z][^>]*)padding\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)padding-bottom\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)padding-left\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)padding-right\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)padding-top\s*:[^;]*;/mg, "$1"],
561
562  [/(<[a-z][^>]*)font\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)font-family\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)font-size\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)font-style\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)font-variant\s*:[^;]*;/mg, "$1"], [/(<[a-z][^>]*)font-weight\s*:[^;]*;/mg, "$1"],
563
564  [/(<[a-z][^>]*)color\s*:[^;]*;/mg, "$1"]
565);
566
567jsToolBar.prototype.removeTextFormating = function(html) {
568  for (let reg in this.removeFormatRegexp) {
569    html = html.replace(this.removeFormatRegexp[reg][0], this.removeFormatRegexp[reg][1]);
570  }
571
572  html = this.tagsoup2xhtml(html);
573  html = html.replace(/style="\s*?"/mgi, '');
574  return html;
575};
576
577/** Toolbar elements
578-------------------------------------------------------- */
579jsToolBar.prototype.elements.blocks.wysiwyg = {
580  list: ['none', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
581  fn: function(opt) {
582    if (opt == 'none') {
583      const blockLevel = this.getBlockLevel();
584      if (blockLevel !== null) {
585        this.replaceNodeByContent(blockLevel);
586      }
587      this.iwin.focus();
588    } else {
589      try {
590        this.iwin.document.execCommand('formatblock', false, '<' + opt + '>');
591      } catch (e) {}
592      this.iwin.focus();
593    }
594  }
595};
596
597jsToolBar.prototype.elements.strong.fn.wysiwyg = function() {
598  this.iwin.document.execCommand('bold', false, null);
599  this.iwin.focus();
600};
601
602jsToolBar.prototype.elements.em.fn.wysiwyg = function() {
603  this.iwin.document.execCommand('italic', false, null);
604  this.iwin.focus();
605};
606
607jsToolBar.prototype.elements.ins.fn.wysiwyg = function() {
608  this.iwin.document.execCommand('underline', false, null);
609  this.iwin.focus();
610};
611
612jsToolBar.prototype.elements.del.fn.wysiwyg = function() {
613  this.iwin.document.execCommand('strikethrough', false, null);
614  this.iwin.focus();
615};
616
617jsToolBar.prototype.elements.quote.fn.wysiwyg = function() {
618  const n = this.getSelectedNode();
619  const q = this.iwin.document.createElement('q');
620  q.appendChild(n);
621  this.insertNode(q);
622};
623
624jsToolBar.prototype.elements.code.fn.wysiwyg = function() {
625  const n = this.getSelectedNode();
626  const code = this.iwin.document.createElement('code');
627  code.appendChild(n);
628  this.insertNode(code);
629};
630
631jsToolBar.prototype.elements.mark.fn.wysiwyg = function() {
632  const n = this.getSelectedNode();
633  const mark = this.iwin.document.createElement('mark');
634  mark.appendChild(n);
635  this.insertNode(mark);
636};
637
638jsToolBar.prototype.elements.br.fn.wysiwyg = function() {
639  const n = this.iwin.document.createElement('br');
640  this.insertNode(n);
641};
642
643jsToolBar.prototype.elements.blockquote.fn.wysiwyg = function() {
644  const n = this.getSelectedNode();
645  const q = this.iwin.document.createElement('blockquote');
646  q.appendChild(n);
647  this.insertNode(q);
648};
649
650jsToolBar.prototype.elements.pre.fn.wysiwyg = function() {
651  this.iwin.document.execCommand('formatblock', false, '<pre>');
652  this.iwin.focus();
653};
654
655jsToolBar.prototype.elements.ul.fn.wysiwyg = function() {
656  this.iwin.document.execCommand('insertunorderedlist', false, null);
657  this.iwin.focus();
658};
659
660jsToolBar.prototype.elements.ol.fn.wysiwyg = function() {
661  this.iwin.document.execCommand('insertorderedlist', false, null);
662  this.iwin.focus();
663};
664
665jsToolBar.prototype.elements.link.fn.wysiwyg = function() {
666  let href;
667  let hreflang;
668  let range;
669  let commonAncestorContainer;
670  if (this.iwin.getSelection) { //gecko
671    const selection = this.iwin.getSelection();
672    range = selection.getRangeAt(0);
673    commonAncestorContainer = range.commonAncestorContainer;
674    while (commonAncestorContainer.nodeType != 1) {
675      commonAncestorContainer = commonAncestorContainer.parentNode;
676    }
677  } else { //ie
678    range = this.iwin.document.selection.createRange();
679    commonAncestorContainer = range.parentElement();
680  }
681
682  let ancestorTagName = commonAncestorContainer.tagName.toLowerCase();
683  while (ancestorTagName != 'a' && ancestorTagName != 'body') {
684    commonAncestorContainer = commonAncestorContainer.parentNode;
685    ancestorTagName = commonAncestorContainer.tagName.toLowerCase();
686  }
687
688  // Update or remove link?
689  if (ancestorTagName == 'a') {
690    href = commonAncestorContainer.href || '';
691    hreflang = commonAncestorContainer.hreflang || '';
692  }
693
694  href = window.prompt(this.elements.link.href_prompt, href);
695
696  // Remove link
697  if (ancestorTagName == 'a' && href == '') {
698    this.replaceNodeByContent(commonAncestorContainer);
699  }
700  if (!href) return; // user cancel
701
702  hreflang = window.prompt(this.elements.link.hreflang_prompt, hreflang);
703
704  // Update link
705  if (ancestorTagName == 'a' && href) {
706    commonAncestorContainer.setAttribute('href', href);
707    if (hreflang) {
708      commonAncestorContainer.setAttribute('hreflang', hreflang);
709    } else {
710      commonAncestorContainer.removeAttribute('hreflang');
711    }
712    return;
713  }
714
715  // Create link
716  const n = this.getSelectedNode();
717  const a = this.iwin.document.createElement('a');
718  a.href = href;
719  if (hreflang) a.setAttribute('hreflang', hreflang);
720  a.appendChild(n);
721  this.insertNode(a);
722};
723
724// Remove format and Toggle
725jsToolBar.prototype.elements.removeFormat = {
726  type: 'button',
727  title: 'Remove text formating',
728  fn: {}
729};
730jsToolBar.prototype.elements.removeFormat.disabled = !jsToolBar.prototype.can_wwg;
731jsToolBar.prototype.elements.removeFormat.fn.xhtml = function() {
732  let html = this.textarea.value;
733  html = this.removeTextFormating(html);
734  this.textarea.value = html;
735};
736jsToolBar.prototype.elements.removeFormat.fn.wysiwyg = function() {
737  let html = this.iwin.document.body.innerHTML;
738  html = this.removeTextFormating(html);
739  this.iwin.document.body.innerHTML = html;
740};
741/** Utilities
742-------------------------------------------------------- */
743function arrayIndexOf(aArray, aValue) {
744  if (typeof Array.indexOf == 'function') {
745    return aArray.indexOf(aValue);
746  } else {
747    let index = -1;
748    const l = aArray.length;
749    for (let i = 0; i < l; i++) {
750      if (aArray[i] === aValue) {
751        index = i;
752        break;
753      }
754    }
755    return index;
756  }
757}
758
759function addEvent(obj, evType, fn, useCapture) {
760  if (obj.addEventListener) {
761    obj.addEventListener(evType, fn, useCapture);
762    return true;
763  } else if (obj.attachEvent) {
764    const r = obj.attachEvent("on" + evType, fn);
765    return r;
766  } else {
767    return false;
768  }
769}
770
771function removeEvent(obj, evType, fn, useCapture) {
772  if (obj.removeEventListener) {
773    obj.removeEventListener(evType, fn, useCapture);
774    return true;
775  } else if (obj.detachEvent) {
776    const r = obj.detachEvent("on" + evType, fn);
777    return r;
778  } else {
779    return false;
780  }
781}
782
783function regexpEscape(s) {
784  return s.replace(/([\\\^\$*+[\]?{}.=!:(|)])/g, "\\$1");
785}
Note: See TracBrowser for help on using the repository browser.

Sites map