Quill tooltip, how to make your own. Example on links

How quill tooltip works, introduction

It's time to tell you how to make your own tooltip. And first, let's find out how the tooltip from quill works.
All Quill tooltips are inherited from the common BaseTooltip class. What should concern me, and you in particular, are the following lines of code:
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();
}
}
I don't like them very much. Why? I just don't see a way to organically add a new tooltip or extend the old one. I myself have been tormented for a long time and wondered how I could extend an existing class. But the only way I came to was to inherit not from BaseTooltip but from Tooltip class.
Another option is to create your own theme. After all, the Quill tooltip is not a module (which would actually be more logical, in my opinion), but a part of the theme. And there are two themes: snow and bubble. But again, for me, this is too much work, just to show one extra window on the screen.
Honestly, my solution to the problem is not elegant at all. But it works ¯\_(ツ)_/¯. And I'm going to show it to you anyway ;)

Developing a custom quill tooltip

As you already know (and if you don't, you can find out (☞゚ヮ゚)☞), my html editor has three types of links: internal, external and downloadable. External leads to other pages. Internal leads to other parts of the same page. Downloadable, well ... these are just links to files that can be downloaded ???
Let's create new blots using inline format. Please note, I do not inherit from 'formats/link' !!! The thing is, this format has a default tooltip attached to it, and I do not need the extra fuss created by this tooltip.
Instead, I created custom blots and registered new formatters for them. I also added click handlers for links. They cause the creation, or rather, the appearance of the tooltip.
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})
A little more about position and height. I use CSS styles to control the position on the screen. In case you wish to see this style, here they are:
.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;
}
I set the height because I wanted it to overlap the "sticky" footer. You do not have to do this.
Now we can move on to the tooltip itself. What does it consist of, what are its features, etc. I created my tooltip by analogy with BaseTooltip. Therefore, many methods are purely for internal use. Here it is:
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 });
}
remove(tooltip){
let text = tooltip.boundsContainer.innerText
tooltip.boundsContainer.outerHTML = text
tooltip.hide();
LinkTooltip.remove();
}
setHeight(height){
this.root.style.height = `${height}px`
}

}
The constructor defines the shape, height, and structure of the tooltip. I want to note that the original tooltip (from quill) defines its structure using a static member of the class called TEMPLATE , which is located in https://github.com/slab/quill/blob/main/packages/quill/src/themes/snow.ts. I decided that I needed a more modular system. (Yes, by modular system I meant those 3 pathetic functions for creating buttons ¯\(°_o)/¯) Here they are:
  1. insertRemoveBtn
  2. insertAddBtn
  3. insertTextInput
The save and remove functions were also redefined. This is how my tooltips work in the editor. Not pretty, not elegant, but they work. I hope this example was useful to you and you learned at least something from it.

heart
0
3 connected dots
0

Related questions