quill
Version:
Your powerful, rich text editor
257 lines • 9.01 kB
JavaScript
import { merge } from 'lodash-es';
import Emitter from '../core/emitter.js';
import Theme from '../core/theme.js';
import ColorPicker from '../ui/color-picker.js';
import IconPicker from '../ui/icon-picker.js';
import Picker from '../ui/picker.js';
import Tooltip from '../ui/tooltip.js';
const ALIGNS = [false, 'center', 'right', 'justify'];
const COLORS = ['#000000', '#e60000', '#ff9900', '#ffff00', '#008a00', '#0066cc', '#9933ff', '#ffffff', '#facccc', '#ffebcc', '#ffffcc', '#cce8cc', '#cce0f5', '#ebd6ff', '#bbbbbb', '#f06666', '#ffc266', '#ffff66', '#66b966', '#66a3e0', '#c285ff', '#888888', '#a10000', '#b26b00', '#b2b200', '#006100', '#0047b2', '#6b24b2', '#444444', '#5c0000', '#663d00', '#666600', '#003700', '#002966', '#3d1466'];
const FONTS = [false, 'serif', 'monospace'];
const HEADERS = ['1', '2', '3', false];
const SIZES = ['small', false, 'large', 'huge'];
class BaseTheme extends Theme {
constructor(quill, options) {
super(quill, options);
const listener = e => {
if (!document.body.contains(quill.root)) {
document.body.removeEventListener('click', listener);
return;
}
if (this.tooltip != null &&
// @ts-expect-error
!this.tooltip.root.contains(e.target) &&
// @ts-expect-error
document.activeElement !== this.tooltip.textbox && !this.quill.hasFocus()) {
this.tooltip.hide();
}
if (this.pickers != null) {
this.pickers.forEach(picker => {
// @ts-expect-error
if (!picker.container.contains(e.target)) {
picker.close();
}
});
}
};
quill.emitter.listenDOM('click', document.body, listener);
}
addModule(name) {
const module = super.addModule(name);
if (name === 'toolbar') {
// @ts-expect-error
this.extendToolbar(module);
}
return module;
}
buildButtons(buttons, icons) {
Array.from(buttons).forEach(button => {
const className = button.getAttribute('class') || '';
className.split(/\s+/).forEach(name => {
if (!name.startsWith('ql-')) return;
name = name.slice('ql-'.length);
if (icons[name] == null) return;
if (name === 'direction') {
// @ts-expect-error
button.innerHTML = icons[name][''] + icons[name].rtl;
} else if (typeof icons[name] === 'string') {
// @ts-expect-error
button.innerHTML = icons[name];
} else {
// @ts-expect-error
const value = button.value || '';
// @ts-expect-error
if (value != null && icons[name][value]) {
// @ts-expect-error
button.innerHTML = icons[name][value];
}
}
});
});
}
buildPickers(selects, icons) {
this.pickers = Array.from(selects).map(select => {
if (select.classList.contains('ql-align')) {
if (select.querySelector('option') == null) {
fillSelect(select, ALIGNS);
}
if (typeof icons.align === 'object') {
return new IconPicker(select, icons.align);
}
}
if (select.classList.contains('ql-background') || select.classList.contains('ql-color')) {
const format = select.classList.contains('ql-background') ? 'background' : 'color';
if (select.querySelector('option') == null) {
fillSelect(select, COLORS, format === 'background' ? '#ffffff' : '#000000');
}
return new ColorPicker(select, icons[format]);
}
if (select.querySelector('option') == null) {
if (select.classList.contains('ql-font')) {
fillSelect(select, FONTS);
} else if (select.classList.contains('ql-header')) {
fillSelect(select, HEADERS);
} else if (select.classList.contains('ql-size')) {
fillSelect(select, SIZES);
}
}
return new Picker(select);
});
const update = () => {
this.pickers.forEach(picker => {
picker.update();
});
};
this.quill.on(Emitter.events.EDITOR_CHANGE, update);
}
}
BaseTheme.DEFAULTS = merge({}, Theme.DEFAULTS, {
modules: {
toolbar: {
handlers: {
formula() {
this.quill.theme.tooltip.edit('formula');
},
image() {
let fileInput = this.container.querySelector('input.ql-image[type=file]');
if (fileInput == null) {
fileInput = document.createElement('input');
fileInput.setAttribute('type', 'file');
fileInput.setAttribute('accept', this.quill.uploader.options.mimetypes.join(', '));
fileInput.classList.add('ql-image');
fileInput.addEventListener('change', () => {
const range = this.quill.getSelection(true);
this.quill.uploader.upload(range, fileInput.files);
fileInput.value = '';
});
this.container.appendChild(fileInput);
}
fileInput.click();
},
video() {
this.quill.theme.tooltip.edit('video');
}
}
}
}
});
class BaseTooltip extends Tooltip {
constructor(quill, boundsContainer) {
super(quill, boundsContainer);
this.textbox = this.root.querySelector('input[type="text"]');
this.listen();
}
listen() {
// @ts-expect-error Fix me later
this.textbox.addEventListener('keydown', event => {
if (event.key === 'Enter') {
this.save();
event.preventDefault();
} else if (event.key === 'Escape') {
this.cancel();
event.preventDefault();
}
});
}
cancel() {
this.hide();
this.restoreFocus();
}
edit() {
let mode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'link';
let preview = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
this.root.classList.remove('ql-hidden');
this.root.classList.add('ql-editing');
if (this.textbox == null) return;
if (preview != null) {
this.textbox.value = preview;
} else if (mode !== this.root.getAttribute('data-mode')) {
this.textbox.value = '';
}
const bounds = this.quill.getBounds(this.quill.selection.savedRange);
if (bounds != null) {
this.position(bounds);
}
this.textbox.select();
this.textbox.setAttribute('placeholder', this.textbox.getAttribute(`data-${mode}`) || '');
this.root.setAttribute('data-mode', mode);
}
restoreFocus() {
this.quill.focus({
preventScroll: true
});
}
save() {
// @ts-expect-error Fix me later
let {
value
} = this.textbox;
switch (this.root.getAttribute('data-mode')) {
case 'link':
{
const {
scrollTop
} = this.quill.root;
if (this.linkRange) {
this.quill.formatText(this.linkRange, 'link', value, Emitter.sources.USER);
delete this.linkRange;
} else {
this.restoreFocus();
this.quill.format('link', value, Emitter.sources.USER);
}
this.quill.root.scrollTop = scrollTop;
break;
}
case 'video':
{
value = extractVideoUrl(value);
}
// eslint-disable-next-line no-fallthrough
case 'formula':
{
if (!value) break;
const range = this.quill.getSelection(true);
if (range != null) {
const index = range.index + range.length;
this.quill.insertEmbed(index,
// @ts-expect-error Fix me later
this.root.getAttribute('data-mode'), value, Emitter.sources.USER);
if (this.root.getAttribute('data-mode') === 'formula') {
this.quill.insertText(index + 1, ' ', Emitter.sources.USER);
}
this.quill.setSelection(index + 2, Emitter.sources.USER);
}
break;
}
default:
}
// @ts-expect-error Fix me later
this.textbox.value = '';
this.hide();
}
}
function extractVideoUrl(url) {
let match = url.match(/^(?:(https?):\/\/)?(?:(?:www|m)\.)?youtube\.com\/watch.*v=([a-zA-Z0-9_-]+)/) || url.match(/^(?:(https?):\/\/)?(?:(?:www|m)\.)?youtu\.be\/([a-zA-Z0-9_-]+)/);
if (match) {
return `${match[1] || 'https'}://www.youtube.com/embed/${match[2]}?showinfo=0`;
}
// eslint-disable-next-line no-cond-assign
if (match = url.match(/^(?:(https?):\/\/)?(?:www\.)?vimeo\.com\/(\d+)/)) {
return `${match[1] || 'https'}://player.vimeo.com/video/${match[2]}/`;
}
return url;
}
function fillSelect(select, values) {
let defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
values.forEach(value => {
const option = document.createElement('option');
if (value === defaultValue) {
option.setAttribute('selected', 'selected');
} else {
option.setAttribute('value', String(value));
}
select.appendChild(option);
});
}
export { BaseTooltip, BaseTheme as default };
//# sourceMappingURL=base.js.map