You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					262 lines
				
				8.5 KiB
			
		
		
			
		
	
	
					262 lines
				
				8.5 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								import Delta from 'quill-delta';
							 | 
						||
| 
								 | 
							
								import Parchment from 'parchment';
							 | 
						||
| 
								 | 
							
								import Quill from '../core/quill';
							 | 
						||
| 
								 | 
							
								import logger from '../core/logger';
							 | 
						||
| 
								 | 
							
								import Module from '../core/module';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								let debug = logger('quill:toolbar');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Toolbar extends Module {
							 | 
						||
| 
								 | 
							
								  constructor(quill, options) {
							 | 
						||
| 
								 | 
							
								    super(quill, options);
							 | 
						||
| 
								 | 
							
								    if (Array.isArray(this.options.container)) {
							 | 
						||
| 
								 | 
							
								      let container = document.createElement('div');
							 | 
						||
| 
								 | 
							
								      addControls(container, this.options.container);
							 | 
						||
| 
								 | 
							
								      quill.container.parentNode.insertBefore(container, quill.container);
							 | 
						||
| 
								 | 
							
								      this.container = container;
							 | 
						||
| 
								 | 
							
								    } else if (typeof this.options.container === 'string') {
							 | 
						||
| 
								 | 
							
								      this.container = document.querySelector(this.options.container);
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      this.container = this.options.container;
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (!(this.container instanceof HTMLElement)) {
							 | 
						||
| 
								 | 
							
								      return debug.error('Container required for toolbar', this.options);
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    this.container.classList.add('ql-toolbar');
							 | 
						||
| 
								 | 
							
								    this.controls = [];
							 | 
						||
| 
								 | 
							
								    this.handlers = {};
							 | 
						||
| 
								 | 
							
								    Object.keys(this.options.handlers).forEach((format) => {
							 | 
						||
| 
								 | 
							
								      this.addHandler(format, this.options.handlers[format]);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    [].forEach.call(this.container.querySelectorAll('button, select'), (input) => {
							 | 
						||
| 
								 | 
							
								      this.attach(input);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    this.quill.on(Quill.events.EDITOR_CHANGE, (type, range) => {
							 | 
						||
| 
								 | 
							
								      if (type === Quill.events.SELECTION_CHANGE) {
							 | 
						||
| 
								 | 
							
								        this.update(range);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    this.quill.on(Quill.events.SCROLL_OPTIMIZE, () => {
							 | 
						||
| 
								 | 
							
								      let [range, ] = this.quill.selection.getRange();  // quill.getSelection triggers update
							 | 
						||
| 
								 | 
							
								      this.update(range);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  addHandler(format, handler) {
							 | 
						||
| 
								 | 
							
								    this.handlers[format] = handler;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  attach(input) {
							 | 
						||
| 
								 | 
							
								    let format = [].find.call(input.classList, (className) => {
							 | 
						||
| 
								 | 
							
								      return className.indexOf('ql-') === 0;
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    if (!format) return;
							 | 
						||
| 
								 | 
							
								    format = format.slice('ql-'.length);
							 | 
						||
| 
								 | 
							
								    if (input.tagName === 'BUTTON') {
							 | 
						||
| 
								 | 
							
								      input.setAttribute('type', 'button');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    if (this.handlers[format] == null) {
							 | 
						||
| 
								 | 
							
								      if (this.quill.scroll.whitelist != null && this.quill.scroll.whitelist[format] == null) {
							 | 
						||
| 
								 | 
							
								        debug.warn('ignoring attaching to disabled format', format, input);
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      if (Parchment.query(format) == null) {
							 | 
						||
| 
								 | 
							
								        debug.warn('ignoring attaching to nonexistent format', format, input);
							 | 
						||
| 
								 | 
							
								        return;
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    let eventName = input.tagName === 'SELECT' ? 'change' : 'click';
							 | 
						||
| 
								 | 
							
								    input.addEventListener(eventName, (e) => {
							 | 
						||
| 
								 | 
							
								      let value;
							 | 
						||
| 
								 | 
							
								      if (input.tagName === 'SELECT') {
							 | 
						||
| 
								 | 
							
								        if (input.selectedIndex < 0) return;
							 | 
						||
| 
								 | 
							
								        let selected = input.options[input.selectedIndex];
							 | 
						||
| 
								 | 
							
								        if (selected.hasAttribute('selected')) {
							 | 
						||
| 
								 | 
							
								          value = false;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          value = selected.value || false;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        if (input.classList.contains('ql-active')) {
							 | 
						||
| 
								 | 
							
								          value = false;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          value = input.value || !input.hasAttribute('value');
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        e.preventDefault();
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      this.quill.focus();
							 | 
						||
| 
								 | 
							
								      let [range, ] = this.quill.selection.getRange();
							 | 
						||
| 
								 | 
							
								      if (this.handlers[format] != null) {
							 | 
						||
| 
								 | 
							
								        this.handlers[format].call(this, value);
							 | 
						||
| 
								 | 
							
								      } else if (Parchment.query(format).prototype instanceof Parchment.Embed) {
							 | 
						||
| 
								 | 
							
								        value = prompt(`Enter ${format}`);
							 | 
						||
| 
								 | 
							
								        if (!value) return;
							 | 
						||
| 
								 | 
							
								        this.quill.updateContents(new Delta()
							 | 
						||
| 
								 | 
							
								          .retain(range.index)
							 | 
						||
| 
								 | 
							
								          .delete(range.length)
							 | 
						||
| 
								 | 
							
								          .insert({ [format]: value })
							 | 
						||
| 
								 | 
							
								        , Quill.sources.USER);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        this.quill.format(format, value, Quill.sources.USER);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      this.update(range);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    // TODO use weakmap
							 | 
						||
| 
								 | 
							
								    this.controls.push([format, input]);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  update(range) {
							 | 
						||
| 
								 | 
							
								    let formats = range == null ? {} : this.quill.getFormat(range);
							 | 
						||
| 
								 | 
							
								    this.controls.forEach(function(pair) {
							 | 
						||
| 
								 | 
							
								      let [format, input] = pair;
							 | 
						||
| 
								 | 
							
								      if (input.tagName === 'SELECT') {
							 | 
						||
| 
								 | 
							
								        let option;
							 | 
						||
| 
								 | 
							
								        if (range == null) {
							 | 
						||
| 
								 | 
							
								          option = null;
							 | 
						||
| 
								 | 
							
								        } else if (formats[format] == null) {
							 | 
						||
| 
								 | 
							
								          option = input.querySelector('option[selected]');
							 | 
						||
| 
								 | 
							
								        } else if (!Array.isArray(formats[format])) {
							 | 
						||
| 
								 | 
							
								          let value = formats[format];
							 | 
						||
| 
								 | 
							
								          if (typeof value === 'string') {
							 | 
						||
| 
								 | 
							
								            value = value.replace(/\"/g, '\\"');
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								          option = input.querySelector(`option[value="${value}"]`);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (option == null) {
							 | 
						||
| 
								 | 
							
								          input.value = '';   // TODO make configurable?
							 | 
						||
| 
								 | 
							
								          input.selectedIndex = -1;
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          option.selected = true;
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        if (range == null) {
							 | 
						||
| 
								 | 
							
								          input.classList.remove('ql-active');
							 | 
						||
| 
								 | 
							
								        } else if (input.hasAttribute('value')) {
							 | 
						||
| 
								 | 
							
								          // both being null should match (default values)
							 | 
						||
| 
								 | 
							
								          // '1' should match with 1 (headers)
							 | 
						||
| 
								 | 
							
								          let isActive = formats[format] === input.getAttribute('value') ||
							 | 
						||
| 
								 | 
							
								                         (formats[format] != null && formats[format].toString() === input.getAttribute('value')) ||
							 | 
						||
| 
								 | 
							
								                         (formats[format] == null && !input.getAttribute('value'));
							 | 
						||
| 
								 | 
							
								          input.classList.toggle('ql-active', isActive);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          input.classList.toggle('ql-active', formats[format] != null);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								Toolbar.DEFAULTS = {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function addButton(container, format, value) {
							 | 
						||
| 
								 | 
							
								  let input = document.createElement('button');
							 | 
						||
| 
								 | 
							
								  input.setAttribute('type', 'button');
							 | 
						||
| 
								 | 
							
								  input.classList.add('ql-' + format);
							 | 
						||
| 
								 | 
							
								  if (value != null) {
							 | 
						||
| 
								 | 
							
								    input.value = value;
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  container.appendChild(input);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function addControls(container, groups) {
							 | 
						||
| 
								 | 
							
								  if (!Array.isArray(groups[0])) {
							 | 
						||
| 
								 | 
							
								    groups = [groups];
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  groups.forEach(function(controls) {
							 | 
						||
| 
								 | 
							
								    let group = document.createElement('span');
							 | 
						||
| 
								 | 
							
								    group.classList.add('ql-formats');
							 | 
						||
| 
								 | 
							
								    controls.forEach(function(control) {
							 | 
						||
| 
								 | 
							
								      if (typeof control === 'string') {
							 | 
						||
| 
								 | 
							
								        addButton(group, control);
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        let format = Object.keys(control)[0];
							 | 
						||
| 
								 | 
							
								        let value = control[format];
							 | 
						||
| 
								 | 
							
								        if (Array.isArray(value)) {
							 | 
						||
| 
								 | 
							
								          addSelect(group, format, value);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          addButton(group, format, value);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								    container.appendChild(group);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function addSelect(container, format, values) {
							 | 
						||
| 
								 | 
							
								  let input = document.createElement('select');
							 | 
						||
| 
								 | 
							
								  input.classList.add('ql-' + format);
							 | 
						||
| 
								 | 
							
								  values.forEach(function(value) {
							 | 
						||
| 
								 | 
							
								    let option = document.createElement('option');
							 | 
						||
| 
								 | 
							
								    if (value !== false) {
							 | 
						||
| 
								 | 
							
								      option.setAttribute('value', value);
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      option.setAttribute('selected', 'selected');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    input.appendChild(option);
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								  container.appendChild(input);
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Toolbar.DEFAULTS = {
							 | 
						||
| 
								 | 
							
								  container: null,
							 | 
						||
| 
								 | 
							
								  handlers: {
							 | 
						||
| 
								 | 
							
								    clean: function() {
							 | 
						||
| 
								 | 
							
								      let range = this.quill.getSelection();
							 | 
						||
| 
								 | 
							
								      if (range == null) return;
							 | 
						||
| 
								 | 
							
								      if (range.length == 0) {
							 | 
						||
| 
								 | 
							
								        let formats = this.quill.getFormat();
							 | 
						||
| 
								 | 
							
								        Object.keys(formats).forEach((name) => {
							 | 
						||
| 
								 | 
							
								          // Clean functionality in existing apps only clean inline formats
							 | 
						||
| 
								 | 
							
								          if (Parchment.query(name, Parchment.Scope.INLINE) != null) {
							 | 
						||
| 
								 | 
							
								            this.quill.format(name, false);
							 | 
						||
| 
								 | 
							
								          }
							 | 
						||
| 
								 | 
							
								        });
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        this.quill.removeFormat(range, Quill.sources.USER);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    direction: function(value) {
							 | 
						||
| 
								 | 
							
								      let align = this.quill.getFormat()['align'];
							 | 
						||
| 
								 | 
							
								      if (value === 'rtl' && align == null) {
							 | 
						||
| 
								 | 
							
								        this.quill.format('align', 'right', Quill.sources.USER);
							 | 
						||
| 
								 | 
							
								      } else if (!value && align === 'right') {
							 | 
						||
| 
								 | 
							
								        this.quill.format('align', false, Quill.sources.USER);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      this.quill.format('direction', value, Quill.sources.USER);
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    indent: function(value) {
							 | 
						||
| 
								 | 
							
								      let range = this.quill.getSelection();
							 | 
						||
| 
								 | 
							
								      let formats = this.quill.getFormat(range);
							 | 
						||
| 
								 | 
							
								      let indent = parseInt(formats.indent || 0);
							 | 
						||
| 
								 | 
							
								      if (value === '+1' || value === '-1') {
							 | 
						||
| 
								 | 
							
								        let modifier = (value === '+1') ? 1 : -1;
							 | 
						||
| 
								 | 
							
								        if (formats.direction === 'rtl') modifier *= -1;
							 | 
						||
| 
								 | 
							
								        this.quill.format('indent', indent + modifier, Quill.sources.USER);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    link: function(value) {
							 | 
						||
| 
								 | 
							
								      if (value === true) {
							 | 
						||
| 
								 | 
							
								        value = prompt('Enter link URL:');
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      this.quill.format('link', value, Quill.sources.USER);
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    list: function(value) {
							 | 
						||
| 
								 | 
							
								      let range = this.quill.getSelection();
							 | 
						||
| 
								 | 
							
								      let formats = this.quill.getFormat(range);
							 | 
						||
| 
								 | 
							
								      if (value === 'check') {
							 | 
						||
| 
								 | 
							
								        if (formats['list'] === 'checked' || formats['list'] === 'unchecked') {
							 | 
						||
| 
								 | 
							
								          this.quill.format('list', false, Quill.sources.USER);
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          this.quill.format('list', 'unchecked', Quill.sources.USER);
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        this.quill.format('list', value, Quill.sources.USER);
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export { Toolbar as default, addControls };
							 |