not really known
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.
 
 
 
 
 

260 lines
8.3 KiB

import Delta from 'quill-delta';
import { EmbedBlot, Scope } from 'parchment';
import Quill from '../core/quill';
import logger from '../core/logger';
import Module from '../core/module';
const debug = logger('quill:toolbar');
class Toolbar extends Module {
constructor(quill, options) {
super(quill, options);
if (Array.isArray(this.options.container)) {
const 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]);
});
Array.from(this.container.querySelectorAll('button, select')).forEach(
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, () => {
const [range] = this.quill.selection.getRange(); // quill.getSelection triggers update
this.update(range);
});
}
addHandler(format, handler) {
this.handlers[format] = handler;
}
attach(input) {
let format = Array.from(input.classList).find(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 &&
this.quill.scroll.query(format) == null
) {
debug.warn('ignoring attaching to nonexistent format', format, input);
return;
}
const eventName = input.tagName === 'SELECT' ? 'change' : 'click';
input.addEventListener(eventName, e => {
let value;
if (input.tagName === 'SELECT') {
if (input.selectedIndex < 0) return;
const 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();
const [range] = this.quill.selection.getRange();
if (this.handlers[format] != null) {
this.handlers[format].call(this, value);
} else if (
this.quill.scroll.query(format).prototype instanceof EmbedBlot
) {
value = prompt(`Enter ${format}`); // eslint-disable-line no-alert
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);
});
this.controls.push([format, input]);
}
update(range) {
const formats = range == null ? {} : this.quill.getFormat(range);
this.controls.forEach(pair => {
const [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)
const 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) {
const 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(controls => {
const group = document.createElement('span');
group.classList.add('ql-formats');
controls.forEach(control => {
if (typeof control === 'string') {
addButton(group, control);
} else {
const format = Object.keys(control)[0];
const value = control[format];
if (Array.isArray(value)) {
addSelect(group, format, value);
} else {
addButton(group, format, value);
}
}
});
container.appendChild(group);
});
}
function addSelect(container, format, values) {
const input = document.createElement('select');
input.classList.add(`ql-${format}`);
values.forEach(value => {
const 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() {
const range = this.quill.getSelection();
if (range == null) return;
if (range.length === 0) {
const formats = this.quill.getFormat();
Object.keys(formats).forEach(name => {
// Clean functionality in existing apps only clean inline formats
if (this.quill.scroll.query(name, Scope.INLINE) != null) {
this.quill.format(name, false, Quill.sources.USER);
}
});
} else {
this.quill.removeFormat(range, Quill.sources.USER);
}
},
direction(value) {
const { align } = this.quill.getFormat();
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(value) {
const range = this.quill.getSelection();
const formats = this.quill.getFormat(range);
const indent = parseInt(formats.indent || 0, 10);
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(value) {
if (value === true) {
value = prompt('Enter link URL:'); // eslint-disable-line no-alert
}
this.quill.format('link', value, Quill.sources.USER);
},
list(value) {
const range = this.quill.getSelection();
const 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 };