Как сделать html тэг для отображения markdown на сайте?

Есть у меня сайт со статьями на html. Я хотел бы сделать кастомный тэг для отображения markdown на сайте. К примеру:

<markdown>
# Заголовок

Текст
- 1
- 2
- 3
</markdown>

Я пробовал сделать что то такое, но знаний у меня не хватило. Помогите пожалуйста.


Ответы (1 шт):

Автор решения: Neverm1ndo

Судя по всему, вам нужны Web Components

Одной из ключевых особенностей веб-компонентов является возможность создания пользовательских элементов: то есть HTML-элементов, поведение которых определяется веб-разработчиком и которые расширяют набор элементов, доступных в браузере.

Простейшая реализация может быть примерно такой:

class MarkdownElement extends HTMLElement {
  /** Список имен обозреваемых атрибутов, сюда можно передать параметры для marked */
  #observableHostAttributeNames = ['async', 'breaks', 'gfm', 'pedantic', 'silent'];
  #hostAttributes = {};
  
  constructor() {
    super();
    this.#initAttributes();
    this.#initContent();
  }
  
  #initAttributes() {
    for(const attr of this.attributes) {
      if (this.#observableHostAttributeNames.includes(attr.name)) {
        this.#hostAttributes[attr.name] = this.#coerceBooleanProperty(attr.value);
      }
    }
  }
  
  #initContent() {
    marked.use(this.#hostAttributes);
  
    /** На этом моменте стоит обратить внимание на защиту от XSS и использовать, например, DOMPurify */
    const raw = marked.parse(this.textContent);    

    this.#clear();
    /** После всех обработок, связанных с безопасностью контента, можно просто использовать innerHtml */
    // this.innerHtml = raw;
    
    /** Или распарсить строку и работать с Document, будет удобнее, если планируете использовать shadow-root */
    const domParser = new DOMParser();
    const markdownNodes = domParser.parseFromString(raw, 'text/html').body.childNodes;
    
    this.append(...markdownNodes);
  }
  
  #clear() {
    this.childNodes.forEach((child) => child.remove());
  }
  
  #coerceBooleanProperty(value) {
    return value != null && `${value}` !== 'false';
  }
}

customElements.define('html-markdown', MarkdownElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/15.0.7/marked.min.js" integrity="sha512-rPuOZPx/WHMHNx2RoALKwiCDiDrCo4ekUctyTYKzBo8NGA79NcTW2gfrbcCL2RYL7RdjX2v9zR0fKyI4U4kPew==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<html-markdown gfm="true" breaks="true">
# Заголовок

Текст
- 1
- 2
- 3
</html-markdown>

Заметьте, что имя элемента должно начинаться со строчной буквы, содержать дефис и соответствовать некоторым другим правилам, перечисленным в определении спецификации

→ Ссылка