262 lines
8.5 KiB
JavaScript
262 lines
8.5 KiB
JavaScript
|
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 };
|