Собственный quill tooltip, как сделать и как работает

Часы
25.10.2024
Глазик
56
Сердечки
0
Соединённые точки
0
Соединённые точки
0
Соединённые точки
0

Как работают тултипы в quill, вместо вступления

Настало время рассказать про то, как сделать свой собственный тултип. И для начала, узнаем как работает тултип от quill.
Все тултипы quill-а, наследуются от общего класса BaseTooltip. Меня, да и тебя в частности, должны волновать следующие строки в коде:
save() {
let { value } = this.textbox;
switch (this.root.getAttribute('data-mode')) {
case 'link': {
...
}
case 'video': {
...
}
case 'formula': {
...
}
default:
}
// @ts-expect-error Fix me later
this.textbox.value = '';
this.hide();
}
}
Мне они не очень нравятся. Почему? Я просто не вижу возможности органично добавить новый тултип или расширить старый. Сам я, долго мучался и гадал, как бы мне расширить уже существующий класс. Но единственный способ, к которому я пришёл это наследоваться не от BaseTooltip, но от Tooltip.
Ещё одним вариантом, является создание своей темы. Ведь quill тултип, это не модуль, (что на самом деле было бы логичнее, по моему мнению), но составная часть темы. А тем две, snow и bubble. Но опят же, по мне, это слишком много работы ради того, чтобы показать на экране одно лишнее окошко.
Честно, моё решение проблемы не элегантно от слова совсем. Но оно рабочее ¯\_(ツ)_/¯. И его я собираюсь показать вам в любом случае)

Собираем тултип

Как ты уже знаешь (а если не знаешь, можешь узнать (☞゚ヮ゚)☞), мой html редактор имеет три типа ссылок, внутренние, внешние и скачиваемые. Внешние, ведут на другие страницы. Внутренние ведут на другие части страницы. Скачиваемые, ну ... это просто ссылки на файлы которые можно скачать ???
Создадим новые blot-ы используя inline. Прошу заметь, я не наследуюсь от 'formats/link' !!! Всё дело в том, что к данному форматеру прикреплён тултип по умолчанию и мне ненужно лишней суеты создаваемой данным тултипом.
Вместо этого, я создал кастомные блоты и зарегистрировал для них новые форматеры. Ещё я добавил обработчики кликов по ссылкам. Они и вызывают создание, вернее сказать, появление тултипа.

// Внутренние ссылки
class InternalLink extends Quill.import('blots/inline'){
constructor(scroll, domNode){
super(scroll, domNode);
domNode.addEventListener('click', (ev) => {
domNode.setAttribute('ref', 'me')
let tooltip = new LinkTooltip(quill, this.domNode, '#ID_SOME')
tooltip.setHeight(document.querySelector('footer').getBoundingClientRect().height)
tooltip.show()
})
}
}
InternalLink.tagName = 'a'
InternalLink.className = 'ref-int'
InternalLink.blotName = 'internal-link'
Quill.register({'formats/internal-link': InternalLink})

// Скачиваеммые ссылки
class DownloadableLink extends Quill.import('blots/inline'){
constructor(scroll, domNode){
super(scroll, domNode);
domNode.setAttribute('ref', 'me')
domNode.addEventListener('click', (ev) => {
let tooltip = new LinkTooltip(quill, this.domNode, '?')
tooltip.setHeight(document.querySelector('footer').getBoundingClientRect().height)
tooltip.show()
tooltip.root.querySelector('input').remove()
tooltip.root.querySelector('.add_button').remove( )
})
}

}
DownloadableLink.tagName = 'a'
DownloadableLink.className = 'ref-downloadable'
DownloadableLink.blotName = 'downloadable-link'
Quill.register({'formats/downloadable-link': DownloadableLink}

// Внешние ссылки
class ExternalLink extends Quill.import('blots/inline'){
constructor(scroll, domNode){
super(scroll, domNode);
domNode.setAttribute('target', '_blank')
domNode.setAttribute('ref', 'noreferrer nofollow external')
domNode.addEventListener('click', (ev) => {
let tooltip = new LinkTooltip(quill, this.domNode, 'http(s)://website.com')
tooltip.setHeight(document.querySelector('footer').getBoundingClientRect().height)
tooltip.show()
})
}
}
ExternalLink.tagName = 'a'
ExternalLink.className = 'ref-ext'
ExternalLink.blotName = 'external-link'
Quill.register({'formats/external-link': ExternalLink})
Ещё немного о позиции и высоте. За положение на экране у меня отвечают CSS стили. Не то чтобы они очень нужны. Но вот они:
.link-tooltip{
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--min_pm);
position: fixed !important;
bottom: 0;
left: 0;
background-color: var(--main_color) !important;
width: 100%;
color: white !important;
transform: translateY(0px) !important;
z-index: 3;
}
Высоту я устанавливаю ибо, хотел чтобы он, перекрывал "прилипший" футер.
Теперь можно перейти к самому тултипу. Из чего он состоит, в чём особенности и т.д. и т.п. Я создавал свой тултип по аналогии с BaseTooltip. По этому, много методов чисто для внутренней работы. Вот весь он:
class LinkTooltip extends Quill.import('ui/tooltip'){
static create(value){
let node = super.create(value)
return node
}

static remove(){
let prev = document.querySelector('.link-tooltip')
if (prev){
prev.remove()
}
}

constructor(scroll, domNode, placeholder = ''){
let prev = document.querySelector('.link-tooltip')
if (prev){
prev.remove()
}
super(scroll, domNode);
this.textbox = null
this.root.innerText = ""
this.root.classList.add('link-tooltip')
this.insertTextInput(placeholder)
this.insertAddBtn()
this.insertRemoveBtn()
}
insertTextInput(placeholder){
this.textbox = document.createElement('input')
this.textbox.setAttribute('type', 'text')
this.textbox.placeholder = placeholder
this.textbox.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
this.save(this);
event.preventDefault();
} else if (event.key === 'Escape') {
this.cancel();
event.preventDefault();
}
});
this.root.insertAdjacentElement('beforeend', this.textbox)
}

insertAddBtn(){
let add = document.createElement('div')
add.classList.add('add_button')
add.classList.add('text_button')
add.innerText = document.querySelector('#save_text').innerText
add.addEventListener('click', () => {this.save(this)})
this.root.insertAdjacentElement('beforeend', add)
}

insertRemoveBtn(){
let add = document.createElement('div')
add.classList.add('remove_button')
add.classList.add('text_button')
add.innerText = document.querySelector('#remove_text').innerText
add.addEventListener('click', () => {this.remove(this)})
this.root.insertAdjacentElement('beforeend', add)
}

cancel() {
this.hide();
this.restoreFocus();
}
restoreFocus() {
this.quill.focus({ preventScroll: true });
}
save(tooltip) {
let value = tooltip.textbox.value;
if (value.length > 0){
tooltip.boundsContainer.parentElement.classList.add('ref')
tooltip.boundsContainer.setAttribute('href', value)
}
LinkTooltip.remove();
}
remove(tooltip){
let text = tooltip.boundsContainer.innerText
tooltip.boundsContainer.outerHTML = text
tooltip.hide();
LinkTooltip.remove();
}
setHeight(height){
this.root.style.height = `${height}px`
}

}
Конструктор задаёт форму, высоту и структуру тултипа. Хочу отметить, что оригинальй тултип (который от quill), задаёт свою структуру при помощи статического члена класса TEMPLATE, что находится в https://github.com/slab/quill/blob/main/packages/quill/src/themes/snow.ts. Я решил, мне нужна более модульная система. (Да, под модульной системой я имел в виду те жалкие 3 функции создания кнопочек ¯\(°_o)/¯) По этому существуют такие функции как:
  1. insertRemoveBtn
  2. insertAddBtn
  3. insertTextInput
Так же были переопределены функции save и remove. Вот таким вот образом работают мои тултипы в редакторе. Не красиво и не элегантно, но работает. Надеюсь, этот пример был тебе полезен и ты подчерпнул из него хоть что-то.

Комментарии

(0)
captcha
Отправить
Сейчас тут пусто. Буть первым (o゚v゚)ノ