Как сделать quill модуль для генерации оглавления статьи

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

Содержание

h2

Вступление

h2

Регистрация модуля и создание для него переключателя

h2

Реализация, генерации оглавления.

h2

Заключение и заметки


Вступление

В этой статье я рассмотрю вопрос о том как создать и добавить собственный модуль в редактор quill. Мы будем делать всё это на примере создания такого полезного модуля как, генератора оглавления статьи по использованным заголовкам.
На официальном сайте уже есть что-то подобное, но там не раскрыты все те проблемы с которыми я столкнулся при создании данного модуля. Например, как отключить модуль. В официальной документации я этого не нашёл. Да и организация подключения модуля, честно говоря, тоже не может быть такой, какой её показали. А так, очень интересная и познавательная статья. ☜(゚ヮ゚☜) Рекомендую.

Регистрация модуля и создание для него переключателя

Пожалуй начну с того, как мы этот модуль подключим (или зарегистрируем). Для этого, первой переменной мы присвоим значение "modules/table_of_contents", а во вторая переменная будет у нас именем инициализирующей функции. Вот так:
Quill.register('modules/table_of_contents', InitTableOfContents);
Теперь к реализации самой инициализирующей функции. Дабы оглавление генерировалось динамически, мы подключим к quill-у функцию. Которая будет вызываться каждый раз при изменении текста.
function InitTableOfContents(quill, options) {
quill.on(Quill.events.TEXT_CHANGE, RunTableOfContents);
}
Подключим наш кастомный модуль к редактору.
const quill = new Quill('#editor', {
modules: {
table_of_contents: true,
toolbar: {
container: '#toolbar-container',
}
},
placeholder: '',
theme: 'snow',
});
Хорошо, мы подключили его, но как его отключать? (⊙_⊙)?А ни как. По крайней мере я не разобрался как. Вернее, я разобрался, но правильный ли это метод я не уверен. Quill имеет методы getModule, addModule, но не имеет deleteModule или removeModule. Что конечно странно.
Как вариант удаления модуля, можно использовать delete на ассоциативном массиве (где, собственно говоря, и содержатся все модули). И это не сработает. Ведь мы ещё добавляем, собственный обработчик на событие изменения текста. И при удалении модуля обработчик останется ¯\(°_o)/¯
Поэтому, моё решение данного вопроса, это не удаление модуля, а удаление непосредственно обработчика событий. В моём случае это RunTableOfContents. На странице html-редактора ты сможешь найти чекбокс для включения и выключения данного модуля. (Как ты уже понял, он удаляет не модуль, но обработчик RunTableOfContents.)
Ниже я представлю код самого обработчика. В чём суть. Все обработчики храняться в _events переменной emmiter. Чтобы добавить и(или) удалить обработчк используй метод(ы) addListenerremoveListener), который (которые) принимает(принимают) 3(4) агрумента, это:
  1. Тип обработчика (Для меня это TEXT_CHANGE)
  2. Имя функции
  3. Контекст (Всегда передавай quill.emmiter)
  4. Булевое значение, которое используется, для того чтобы запустить передаваемую функцию, единожды.
document.querySelector('#toc_module').addEventListener('click', (event)=>{
  if (event.target.checked){
    quill.emitter.addListener(Quill.events.TEXT_CHANGE, RunTableOfContents, quill.emitter)
  }else{
    quill.emitter.removeListener(Quill.events.TEXT_CHANGE, RunTableOfContents, quill.emitter, false)
  }
})
С подключением и настройкой переключателя мы закончили. Давай непосредственно взглянем на реализацию.

Реализация, генерации оглавления.

Сразу хочу пояснить, я не добавлял тег H1, ибо в первую очередь создавал инструмент для себя. И данный тег в генерации оглавления не участвует. Ты в свою очередь волен поменять, повернуть и разобрать код на столько кусочков, на сколько сможешь. Никто тебя не остановит (~ ̄▽ ̄)~.
Вот код обработчика RunTableOfContents.
function RunTableOfContents(){
let range = quill.getSelection()
let blot = Quill.find(quill.getLeaf(range.index)[0].domNode).parent
let DOMnode = blot.domNode
switch (DOMnode.tagName){
case 'H2':
case 'H3':
case 'H4':
case 'H5':
case 'H6':
document.querySelectorAll('.table_of_contents').forEach((el) => {
let prev = quill.scroll.find(el)
if (prev){
prev.remove()
}
})
var container = quill.scroll.create(TableOfContents.blotName)
var ref = quill.scroll.children.head
quill.scroll.insertBefore(container, ref)
break
}
}
Данный обработчик находит blot на котором сейчас находится курсор и проверяет его тип по тегу. Если курсор на тегах оглавлений, то есть от h2 до h6, он обновляет оглавление предварительно удалив предыдущее.
Для оглавления, я использую кастомный форматер. Вот как я его реализовал и зарегистрировал.
class TableOfContents extends Quill.import('blots/block'){
static create(value){
let node = super.create(value);
return node
}
constructor(scroll, domNode){
super(scroll, domNode)
var header = document.createElement('h2')
header.innerText = document.querySelector('#table_of_content_text').innerText
header.style.marginTop = '0'
header.style.borderBottom = '2px solid gray'
header.style.marginBottom = '0'
domNode.insertAdjacentElement('beforeend', header)

document.querySelectorAll('h2,h3,h4,h5,h6').forEach( (heading) => {
if (!heading.classList.contains('table_of_contents')){
var container_headers_element = document.createElement('div')
let tag_name = heading.nodeName.toLowerCase()
let padding = "padder-5"
if( tag_name == 'h2')
padding = "padder-5"
else if (tag_name == 'h3')
padding = "padder-10"
else if (tag_name == 'h4')
padding = "padder-15"
else if (tag_name == 'h5')
padding = "padder-20"
else if (tag_name == 'h6')
padding = "padder-25"
container_headers_element.classList.add(padding)
container_headers_element.style.display = 'flex'
container_headers_element.style.gap = '8px'
container_headers_element.style.alignItems = 'center'
container_headers_element.style.marginTop = '10px'
var container_headers_sign = document.createElement('p')
container_headers_sign.innerText = tag_name
container_headers_sign.style.border = '1px solid grey'
container_headers_sign.style.borderRadius = '50%'
container_headers_sign.style.padding = '5px'
container_headers_sign.style.color = 'grey'
container_headers_sign.style.fontSize = '10px'
var container_headers_text = document.createElement('p')
container_headers_text.innerText = heading.innerText
container_headers_element.insertAdjacentElement('beforeend', container_headers_sign)
container_headers_element.insertAdjacentElement('beforeend', container_headers_text)
this.domNode.insertAdjacentElement('beforeend', container_headers_element)
}
})
}
}
TableOfContents.tagName = 'div'
TableOfContents.blotName = 'table_of_contents'
TableOfContents.className = 'table_of_contents'

Quill.register({'formats/table_of_contents': TableOfContents})
В моменте, я хотел реализовать его как контейнер (то есть расширить его от blots/container). Но подумал, что это слишком, для данного модуля, и не стоит его усложнять.
И ещё. Я мог использовать CSS селекторы для стилизации, но подумал, что наверное для читателя будет проще воспринять именно такую форму, без необходимости ещё и учитывать каскадные стили.

Заключение и заметки

На самом деле, для корректной работы данного модуля необходимо ещё проводить дополнительную очистку редактора. Так как мой кастомный форматер не реализован в полной своей мере. Я провожу очистку в функции loadQuill. Сразу после того, как загрузил содержимое на страницу.
function loadQuill( content ){
var scope = document.querySelector('.ql-editor')
scope.innerHTML = content
var table_of_content = scope.querySelector('.table_of_contents')
if (table_of_content){
table_of_content.remove()
}
}
Ещё, я не сделал оглавление динамическим, то есть не добавлял внутренних ссылок на соответствующие заголовки. Простите, поленился. (ಥ _ ಥ)
А так, работоспособность данного модуля можно оценить на странице моего rich quill редактора. Так же, если ты вдруг не заметил, в самом начале этой статьи есть соответствующий блок с оглавлением. Посмотри, полюбуйся (⓿_⓿)

Комментарии

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