Dotclear

source: plugins/dcLegacyEditor/js/jsToolBar/jsToolBar.js @ 3880:e6d1f6d9d7df

Revision 3880:e6d1f6d9d7df, 17.0 KB checked in by franck <carnet.franck.paul@…>, 7 years ago (diff)

Use let and const rather than var (ES2015/ES6), use template string where is more efficient

Line 
1/*exported jsToolBar */
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
26const jsToolBar = function(textarea) {
27  if (!document.createElement) {
28    return;
29  }
30
31  if (!textarea) {
32    return;
33  }
34
35  if ((typeof(document.selection) == "undefined") && (typeof(textarea.setSelectionRange) == "undefined")) {
36    return;
37  }
38
39  this.textarea = textarea;
40
41  this.editor = document.createElement('div');
42  this.editor.className = 'jstEditor';
43
44  this.textarea.parentNode.insertBefore(this.editor, this.textarea);
45  this.editor.appendChild(this.textarea);
46
47  this.toolbar = document.createElement("div");
48  this.toolbar.className = 'jstElements';
49
50  if (this.toolbar_bottom) {
51    this.editor.parentNode.insertBefore(this.toolbar, this.editor.nextSibling);
52  } else {
53    this.editor.parentNode.insertBefore(this.toolbar, this.editor);
54  }
55
56  // Dragable resizing (only for gecko)
57  if (navigator.appName == 'Microsoft Internet Explorer') {
58    if (this.editor.addEventListener) {
59      this.handle = document.createElement('div');
60      this.handle.className = 'jstHandle';
61      const dragStart = this.resizeDragStart;
62      const This = this;
63      this.handle.addEventListener('mousedown', function(event) {
64        dragStart.call(This, event);
65      }, false);
66      this.editor.parentNode.insertBefore(this.handle, this.editor.nextSibling);
67    }
68  }
69
70  this.context = null;
71  this.toolNodes = {}; // lorsque la toolbar est dessinée , cet objet est garni
72  // de raccourcis vers les éléments DOM correspondants aux outils.
73};
74
75const jsButton = function(title, fn, scope, className, accesskey) {
76  this.title = title || null;
77  this.fn = fn || function() {};
78  this.scope = scope || null;
79  this.className = className || null;
80  this.accesskey = accesskey || null;
81};
82jsButton.prototype.draw = function() {
83  if (!this.scope) return null;
84
85  const button = document.createElement('button');
86  button.setAttribute('type', 'button');
87  if (this.className) button.className = this.className;
88  button.title = this.title;
89  if (this.accesskey) button.accessKey = this.accesskey;
90  const span = document.createElement('span');
91  span.appendChild(document.createTextNode(this.title));
92  button.appendChild(span);
93
94  if (this.icon != undefined) {
95    button.style.backgroundImage = 'url(' + this.icon + ')';
96  }
97  if (typeof(this.fn) == 'function') {
98    const This = this;
99    button.onclick = function() {
100      try {
101        This.fn.apply(This.scope, arguments);
102      } catch (e) {}
103      return false;
104    };
105  }
106  return button;
107};
108
109const jsSpace = function(id) {
110  this.id = id || null;
111  this.width = null;
112};
113jsSpace.prototype.draw = function() {
114  const span = document.createElement('span');
115  if (this.id) span.id = this.id;
116  span.appendChild(document.createTextNode(String.fromCharCode(160)));
117  span.className = 'jstSpacer';
118  if (this.width) span.style.marginRight = this.width + 'px';
119
120  return span;
121};
122
123const jsCombo = function(title, options, scope, fn, className) {
124  this.title = title || null;
125  this.options = options || null;
126  this.scope = scope || null;
127  this.fn = fn || function() {};
128  this.className = className || null;
129};
130jsCombo.prototype.draw = function() {
131  if (!this.scope || !this.options) return null;
132
133  const select = document.createElement('select');
134  if (this.className) select.className = this.className;
135  select.title = this.title;
136
137  for (let o in this.options) {
138    //var opt = this.options[o];
139    const option = document.createElement('option');
140    option.value = o;
141    option.appendChild(document.createTextNode(this.options[o]));
142    select.appendChild(option);
143  }
144
145  const This = this;
146  select.onchange = function() {
147    try {
148      This.fn.call(This.scope, this.value);
149    } catch (e) {
150      window.alert(e);
151    }
152
153    return false;
154  };
155
156  return select;
157};
158
159jsToolBar.prototype = {
160  base_url: '',
161  mode: 'xhtml',
162  elements: {},
163  toolbar_bottom: false,
164
165  getMode: function() {
166    return this.mode;
167  },
168
169  setMode: function(mode) {
170    this.mode = mode || 'xhtml';
171  },
172
173  switchMode: function(mode) {
174    mode = mode || 'xhtml';
175    this.draw(mode);
176  },
177
178  button: function(toolName) {
179    const tool = this.elements[toolName];
180    if (typeof tool.fn[this.mode] != 'function') return null;
181    const b = new jsButton(tool.title, tool.fn[this.mode], this, 'jstb_' + toolName, tool.accesskey);
182    if (tool.icon != undefined) {
183      b.icon = tool.icon;
184    }
185    return b;
186  },
187  space: function(toolName) {
188    const tool = new jsSpace(toolName);
189    if (this.elements[toolName].format != undefined && !this.elements[toolName].format[this.mode]) return null;
190    if (this.elements[toolName].width !== undefined) {
191      tool.width = this.elements[toolName].width;
192    }
193    return tool;
194  },
195  combo: function(toolName) {
196    const tool = this.elements[toolName];
197
198    if (tool[this.mode] != undefined) {
199
200      const length = tool[this.mode].list.length;
201
202      if (typeof tool[this.mode].fn != 'function' || length == 0) {
203        return null;
204      } else {
205        let options = {};
206        for (let i = 0; i < length; i++) {
207          const opt = tool[this.mode].list[i];
208          options[opt] = tool.options[opt];
209        }
210        return new jsCombo(tool.title, options, this, tool[this.mode].fn);
211      }
212
213    }
214
215  },
216  draw: function(mode) {
217    this.setMode(mode);
218
219    // Empty toolbar
220    while (this.toolbar.hasChildNodes()) {
221      this.toolbar.removeChild(this.toolbar.firstChild);
222    }
223    this.toolNodes = {}; // vide les raccourcis DOM/**/
224
225    // Draw toolbar elements
226    let b;
227    let tool;
228    let newTool;
229
230    for (let i in this.elements) {
231      b = this.elements[i];
232
233      const disabled =
234        b.type == undefined || b.type == '' ||
235        (b.disabled != undefined && b.disabled) ||
236        (b.context != undefined && b.context != null && b.context != this.context);
237
238      if (!disabled && typeof this[b.type] == 'function') {
239        tool = this[b.type](i);
240        if (tool) newTool = tool.draw();
241        if (newTool) {
242          this.toolNodes[i] = newTool; //mémorise l'accès DOM pour usage éventuel ultérieur
243          this.toolbar.appendChild(newTool);
244        }
245      }
246    }
247  },
248
249  singleTag: function(stag, etag) {
250    stag = stag || null;
251    etag = etag || stag;
252
253    if (!stag || !etag) {
254      return;
255    }
256
257    this.encloseSelection(stag, etag);
258  },
259
260  encloseSelection: function(prefix, suffix, fn) {
261    this.textarea.focus();
262
263    prefix = prefix || '';
264    suffix = suffix || '';
265
266    let start;
267    let end;
268    let sel;
269    let scrollPos;
270    let subst;
271    let res;
272
273    if (typeof(document.selection) != "undefined") {
274      sel = document.selection.createRange().text;
275    } else if (typeof(this.textarea.setSelectionRange) != "undefined") {
276      start = this.textarea.selectionStart;
277      end = this.textarea.selectionEnd;
278      scrollPos = this.textarea.scrollTop;
279      sel = this.textarea.value.substring(start, end);
280    }
281
282    if (sel.match(/ $/)) { // exclude ending space char, if any
283      sel = sel.substring(0, sel.length - 1);
284      suffix = suffix + " ";
285    }
286
287    if (typeof(fn) == 'function') {
288      res = (sel) ? fn.call(this, sel) : fn('');
289    } else {
290      res = (sel) ? sel : '';
291    }
292
293    subst = prefix + res + suffix;
294
295    if (typeof(document.selection) != "undefined") {
296      document.selection.createRange().text = subst;
297      this.textarea.caretPos -= suffix.length;
298    } else if (typeof(this.textarea.setSelectionRange) != "undefined") {
299      this.textarea.value = this.textarea.value.substring(0, start) + subst +
300        this.textarea.value.substring(end);
301      if (sel) {
302        this.textarea.setSelectionRange(start + subst.length, start + subst.length);
303      } else {
304        if (typeof(fn) != 'function') {
305          this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
306        }
307      }
308      this.textarea.scrollTop = scrollPos;
309    }
310  },
311
312  stripBaseURL: function(url) {
313    if (this.base_url != '') {
314      const pos = url.indexOf(this.base_url);
315      if (pos == 0) {
316        url = url.substr(this.base_url.length);
317      }
318    }
319
320    return url;
321  }
322};
323
324/** Resizer
325-------------------------------------------------------- */
326jsToolBar.prototype.resizeSetStartH = function() {
327  this.dragStartH = this.textarea.offsetHeight + 0;
328};
329jsToolBar.prototype.resizeDragStart = function(event) {
330  const This = this;
331  this.dragStartY = event.clientY;
332  this.resizeSetStartH();
333  document.addEventListener('mousemove', this.dragMoveHdlr = function(event) {
334    This.resizeDragMove(event);
335  }, false);
336  document.addEventListener('mouseup', this.dragStopHdlr = function(event) {
337    This.resizeDragStop(event);
338  }, false);
339};
340
341jsToolBar.prototype.resizeDragMove = function(event) {
342  this.textarea.style.height = (this.dragStartH + event.clientY - this.dragStartY) + 'px';
343};
344
345jsToolBar.prototype.resizeDragStop = function() {
346  document.removeEventListener('mousemove', this.dragMoveHdlr, false);
347  document.removeEventListener('mouseup', this.dragStopHdlr, false);
348};
349
350// Elements definition ------------------------------------
351// block format (paragraph, headers)
352jsToolBar.prototype.elements.blocks = {
353  type: 'combo',
354  title: 'block format',
355  options: {
356    none: '-- none --', // only for wysiwyg mode
357    nonebis: '- block format -', // only for xhtml mode
358    p: 'Paragraph',
359    h1: 'Header 1',
360    h2: 'Header 2',
361    h3: 'Header 3',
362    h4: 'Header 4',
363    h5: 'Header 5',
364    h6: 'Header 6'
365  },
366  xhtml: {
367    list: ['nonebis', 'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
368    fn: function(opt) {
369      if (opt == 'nonebis')
370        this.textarea.focus();
371      else
372        try {
373          this.singleTag(`<${opt}>`, `</${opt}>`);
374        } catch (e) {}
375      this.toolNodes.blocks.value = 'nonebis';
376    }
377  },
378  wiki: {
379    list: ['nonebis', 'h3', 'h4', 'h5'],
380    fn: function(opt) {
381      switch (opt) {
382        case 'nonebis':
383          this.textarea.focus();
384          break;
385        case 'h3':
386          this.encloseSelection('!!!');
387          break;
388        case 'h4':
389          this.encloseSelection('!!');
390          break;
391        case 'h5':
392          this.encloseSelection('!');
393          break;
394      }
395      this.toolNodes.blocks.value = 'nonebis';
396    }
397  }
398};
399
400// spacer
401jsToolBar.prototype.elements.space0 = {
402  type: 'space',
403  format: {
404    wysiwyg: true,
405    wiki: true,
406    xhtml: true
407  }
408};
409
410// strong
411jsToolBar.prototype.elements.strong = {
412  type: 'button',
413  title: 'Strong emphasis',
414  fn: {
415    wiki: function() {
416      this.singleTag('__');
417    },
418    xhtml: function() {
419      this.singleTag('<strong>', '</strong>');
420    }
421  }
422};
423
424// em
425jsToolBar.prototype.elements.em = {
426  type: 'button',
427  title: 'Emphasis',
428  fn: {
429    wiki: function() {
430      this.singleTag("''");
431    },
432    xhtml: function() {
433      this.singleTag('<em>', '</em>');
434    }
435  }
436};
437
438// ins
439jsToolBar.prototype.elements.ins = {
440  type: 'button',
441  title: 'Inserted',
442  fn: {
443    wiki: function() {
444      this.singleTag('++');
445    },
446    xhtml: function() {
447      this.singleTag('<ins>', '</ins>');
448    }
449  }
450};
451
452// del
453jsToolBar.prototype.elements.del = {
454  type: 'button',
455  title: 'Deleted',
456  fn: {
457    wiki: function() {
458      this.singleTag('--');
459    },
460    xhtml: function() {
461      this.singleTag('<del>', '</del>');
462    }
463  }
464};
465
466// quote
467jsToolBar.prototype.elements.quote = {
468  type: 'button',
469  title: 'Inline quote',
470  fn: {
471    wiki: function() {
472      this.singleTag('{{', '}}');
473    },
474    xhtml: function() {
475      this.singleTag('<q>', '</q>');
476    }
477  }
478};
479
480// code
481jsToolBar.prototype.elements.code = {
482  type: 'button',
483  title: 'Code',
484  fn: {
485    wiki: function() {
486      this.singleTag('@@');
487    },
488    xhtml: function() {
489      this.singleTag('<code>', '</code>');
490    }
491  }
492};
493
494// code
495jsToolBar.prototype.elements.mark = {
496  type: 'button',
497  title: 'Mark',
498  fn: {
499    wiki: function() {
500      this.singleTag('""');
501    },
502    xhtml: function() {
503      this.singleTag('<mark>', '</mark>');
504    }
505  }
506};
507
508// spacer
509jsToolBar.prototype.elements.space1 = {
510  type: 'space',
511  format: {
512    wysiwyg: true,
513    wiki: true,
514    xhtml: true
515  }
516};
517
518// br
519jsToolBar.prototype.elements.br = {
520  type: 'button',
521  title: 'Line break',
522  fn: {
523    wiki: function() {
524      this.encloseSelection("%%%\n", '');
525    },
526    xhtml: function() {
527      this.encloseSelection("<br />\n", '');
528    }
529  }
530};
531
532// spacer
533jsToolBar.prototype.elements.space2 = {
534  type: 'space',
535  format: {
536    wysiwyg: true,
537    wiki: true,
538    xhtml: true
539  }
540};
541
542// blockquote
543jsToolBar.prototype.elements.blockquote = {
544  type: 'button',
545  title: 'Blockquote',
546  fn: {
547    xhtml: function() {
548      this.singleTag('<blockquote>', '</blockquote>');
549    },
550    wiki: function() {
551      this.encloseSelection("\n", '',
552        function(str) {
553          str = str.replace(/\r/g, '');
554          return '> ' + str.replace(/\n/g, "\n> ");
555        });
556    }
557  }
558};
559
560// pre
561jsToolBar.prototype.elements.pre = {
562  type: 'button',
563  title: 'Preformated text',
564  fn: {
565    wiki: function() {
566      this.singleTag("///\n", "\n///");
567    },
568    xhtml: function() {
569      this.singleTag('<pre>', '</pre>');
570    }
571  }
572};
573
574// ul
575jsToolBar.prototype.elements.ul = {
576  type: 'button',
577  title: 'Unordered list',
578  fn: {
579    wiki: function() {
580      this.encloseSelection('', '', function(str) {
581        str = str.replace(/\r/g, '');
582        return '* ' + str.replace(/\n/g, "\n* ");
583      });
584    },
585    xhtml: function() {
586      this.encloseSelection('', '', function(str) {
587        str = str.replace(/\r/g, '');
588        str = str.replace(/\n/g, "</li>\n <li>");
589        return "<ul>\n <li>" + str + "</li>\n</ul>";
590      });
591    }
592  }
593};
594
595// ol
596jsToolBar.prototype.elements.ol = {
597  type: 'button',
598  title: 'Ordered list',
599  fn: {
600    wiki: function() {
601      this.encloseSelection('', '', function(str) {
602        str = str.replace(/\r/g, '');
603        return '# ' + str.replace(/\n/g, "\n# ");
604      });
605    },
606    xhtml: function() {
607      this.encloseSelection('', '', function(str) {
608        str = str.replace(/\r/g, '');
609        str = str.replace(/\n/g, "</li>\n <li>");
610        return "<ol>\n <li>" + str + "</li>\n</ol>";
611      });
612    }
613  }
614};
615
616// spacer
617jsToolBar.prototype.elements.space3 = {
618  type: 'space',
619  format: {
620    wysiwyg: true,
621    wiki: true,
622    xhtml: true
623  }
624};
625
626// link
627jsToolBar.prototype.elements.link = {
628  type: 'button',
629  title: 'Link',
630  fn: {},
631  accesskey: 'l',
632  href_prompt: 'Please give page URL:',
633  hreflang_prompt: 'Language of this page:',
634  default_hreflang: '',
635  prompt: function(href, hreflang) {
636    href = href || '';
637    hreflang = hreflang || this.elements.link.default_hreflang;
638
639    href = window.prompt(this.elements.link.href_prompt, href);
640    if (!href) {
641      return false;
642    }
643
644    hreflang = window.prompt(this.elements.link.hreflang_prompt,
645      hreflang);
646
647    return {
648      href: this.stripBaseURL(href),
649      hreflang: hreflang
650    };
651  }
652};
653
654jsToolBar.prototype.elements.link.fn.xhtml = function() {
655  var link = this.elements.link.prompt.call(this);
656  if (link) {
657    var stag = '<a href="' + link.href + '"';
658    if (link.hreflang) {
659      stag = stag + ' hreflang="' + link.hreflang + '"';
660    }
661    stag = stag + '>';
662    var etag = '</a>';
663
664    this.encloseSelection(stag, etag);
665  }
666};
667jsToolBar.prototype.elements.link.fn.wiki = function() {
668  var link = this.elements.link.prompt.call(this);
669  if (link) {
670    var stag = '[';
671    var etag = '|' + link.href;
672    if (link.hreflang) {
673      etag = etag + '|' + link.hreflang;
674    }
675    etag = etag + ']';
676
677    this.encloseSelection(stag, etag);
678  }
679};
680
681// img
682jsToolBar.prototype.elements.img = {
683  type: 'button',
684  title: 'External image',
685  src_prompt: 'Please give image URL:',
686  fn: {},
687  prompt: function(src) {
688    src = src || '';
689    return this.stripBaseURL(window.prompt(this.elements.img.src_prompt, src));
690  }
691};
692jsToolBar.prototype.elements.img.fn.xhtml = function() {
693  var src = this.elements.img.prompt.call(this);
694  if (src) {
695    this.encloseSelection('', '', function(str) {
696      if (str) {
697        return `<img src="${src}" alt="${str}" />`;
698      } else {
699        return `<img src="${src}" alt="" />`;
700      }
701    });
702  }
703};
704jsToolBar.prototype.elements.img.fn.wiki = function() {
705  var src = this.elements.img.prompt.call(this);
706  if (src) {
707    this.encloseSelection('', '', function(str) {
708      if (str) {
709        return `((${src}|${str}))`;
710      } else {
711        return `((${src}))`;
712      }
713    });
714  }
715};
Note: See TracBrowser for help on using the repository browser.

Sites map