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.
257 lines
8.8 KiB
257 lines
8.8 KiB
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
|