Standard NotesにMarkdownエディタを追加

Standard Notesに高機能Markdownエディタを追加(非公式)。ライブプレビュー・ツールバー・画像ペースト/アップロード・PDF出力機能を備え、不要画像は自動削除されます。

2025/06/21のページです。最新版はこちら

作者のサイトでサポートを受ける。または、このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Add Markdown Editor to Standard Notes
// @name:ja      Standard NotesにMarkdownエディタを追加
// @name:en      Add Markdown Editor to Standard Notes
// @name:zh-CN   为Standard Notes添加Markdown编辑器
// @name:zh-TW   為Standard Notes新增Markdown編輯器
// @name:ko      Standard Notes에 Markdown 에디터 추가
// @name:fr      Ajouter un éditeur Markdown à Standard Notes
// @name:es      Añadir editor Markdown a Standard Notes
// @name:de      Markdown-Editor zu Standard Notes hinzufügen
// @name:pt-BR   Adicionar editor Markdown ao Standard Notes
// @name:ru      Добавить редактор Markdown в Standard Notes
// @version      2.6.0
// @description         Adds a powerful Markdown editor with live preview, toolbar, image pasting/upload, and PDF export to Standard Notes. Unofficial enhancement with auto-cleanup for unused images.
// @description:ja      Standard Notesに高機能Markdownエディタを追加(非公式)。ライブプレビュー・ツールバー・画像ペースト/アップロード・PDF出力機能を備え、不要画像は自動削除されます。
// @description:zh-CN   为Standard Notes添加功能强大的Markdown编辑器(非官方),支持实时预览、工具栏、图片粘贴/上传和PDF导出,未使用图片会自动清理。
// @description:zh-TW   為Standard Notes新增強大的Markdown編輯器(非官方),具備即時預覽、工具列、圖片貼上/上傳及PDF匯出功能,並自動清除未使用圖片。
// @description:ko      Standard Notes에 강력한 Markdown 에디터를 추가합니다(비공식). 실시간 미리보기, 도구 모음, 이미지 붙여넣기/업로드, PDF 내보내기를 지원하며, 사용하지 않는 이미지는 자동 정리됩니다.
// @description:fr      Ajoute un éditeur Markdown puissant à Standard Notes (non officiel) avec aperçu en direct, barre d'outils, collage/téléversement d’images, export PDF et nettoyage automatique des images inutilisées.
// @description:es      Añade un potente editor Markdown a Standard Notes (no oficial), con vista previa en vivo, barra de herramientas, pegado/carga de imágenes y exportación a PDF. Limpieza automática de imágenes no utilizadas.
// @description:de      Fügt Standard Notes einen leistungsstarken Markdown-Editor hinzu (inoffiziell) – mit Live-Vorschau, Symbolleiste, Bild-Einfügen/-Hochladen, PDF-Export und automatischer Bildbereinigung.
// @description:pt-BR   Adiciona um editor Markdown poderoso ao Standard Notes (não oficial), com pré-visualização ao vivo, barra de ferramentas, colagem/envio de imagens e exportação em PDF. Imagens não usadas são removidas automaticamente.
// @description:ru      Добавляет в Standard Notes мощный редактор Markdown (неофициально) с живым предварительным просмотром, панелью инструментов, вставкой/загрузкой изображений и экспортом в PDF. Неиспользуемые изображения автоматически очищаются.
// @namespace    https://github.com/koyasi777/standardnotes-markdown-enhancer
// @author       koyasi777
// @match        https://app.standardnotes.com/*
// @grant        GM_addStyle
// @license      MIT
// @homepageURL  https://github.com/koyasi777/standardnotes-markdown-enhancer
// @supportURL   https://github.com/koyasi777/standardnotes-markdown-enhancer/issues
// @icon         https://app.standardnotes.com/favicon/favicon-32x32.png
// ==/UserScript==

(function () {
  'use strict';

  /**
   * marked v15.0.12 - a markdown parser
   * Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed)
   * https://github.com/markedjs/marked
   */

  /**
   * DO NOT EDIT THIS FILE
   * The code in this file is generated from files in ./src/
   */


  // src/defaults.ts
  function _getDefaults() {
    return {
      async: false,
      breaks: false,
      extensions: null,
      gfm: true,
      hooks: null,
      pedantic: false,
      renderer: null,
      silent: false,
      tokenizer: null,
      walkTokens: null
    };
  }
  var _defaults = _getDefaults();
  function changeDefaults(newDefaults) {
    _defaults = newDefaults;
  }

  // src/rules.ts
  var noopTest = { exec: () => null };
  function edit(regex, opt = "") {
    let source = typeof regex === "string" ? regex : regex.source;
    const obj = {
      replace: (name, val) => {
        let valSource = typeof val === "string" ? val : val.source;
        valSource = valSource.replace(other.caret, "$1");
        source = source.replace(name, valSource);
        return obj;
      },
      getRegex: () => {
        return new RegExp(source, opt);
      }
    };
    return obj;
  }
  var other = {
    codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm,
    outputLinkReplace: /\\([\[\]])/g,
    indentCodeCompensation: /^(\s+)(?:```)/,
    beginningSpace: /^\s+/,
    endingHash: /#$/,
    startingSpaceChar: /^ /,
    endingSpaceChar: / $/,
    nonSpaceChar: /[^ ]/,
    newLineCharGlobal: /\n/g,
    tabCharGlobal: /\t/g,
    multipleSpaceGlobal: /\s+/g,
    blankLine: /^[ \t]*$/,
    doubleBlankLine: /\n[ \t]*\n[ \t]*$/,
    blockquoteStart: /^ {0,3}>/,
    blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g,
    blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm,
    listReplaceTabs: /^\t+/,
    listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g,
    listIsTask: /^\[[ xX]\] /,
    listReplaceTask: /^\[[ xX]\] +/,
    anyLine: /\n.*\n/,
    hrefBrackets: /^<(.*)>$/,
    tableDelimiter: /[:|]/,
    tableAlignChars: /^\||\| *$/g,
    tableRowBlankLine: /\n[ \t]*$/,
    tableAlignRight: /^ *-+: *$/,
    tableAlignCenter: /^ *:-+: *$/,
    tableAlignLeft: /^ *:-+ *$/,
    startATag: /^<a /i,
    endATag: /^<\/a>/i,
    startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i,
    endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i,
    startAngleBracket: /^</,
    endAngleBracket: />$/,
    pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/,
    unicodeAlphaNumeric: /[\p{L}\p{N}]/u,
    escapeTest: /[&<>"']/,
    escapeReplace: /[&<>"']/g,
    escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,
    escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,
    unescapeTest: /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,
    caret: /(^|[^\[])\^/g,
    percentDecode: /%25/g,
    findPipe: /\|/g,
    splitPipe: / \|/,
    slashPipe: /\\\|/g,
    carriageReturn: /\r\n|\r/g,
    spaceLine: /^ +$/gm,
    notSpaceStart: /^\S*/,
    endingNewline: /\n$/,
    listItemRegex: (bull) => new RegExp(`^( {0,3}${bull})((?:[	 ][^\\n]*)?(?:\\n|$))`),
    nextBulletRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ 	][^\\n]*)?(?:\\n|$))`),
    hrRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),
    fencesBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`),
    headingBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`),
    htmlBeginRegex: (indent) => new RegExp(`^ {0,${Math.min(3, indent - 1)}}<(?:[a-z].*>|!--)`, "i")
  };
  var newline = /^(?:[ \t]*(?:\n|$))+/;
  var blockCode = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
  var fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
  var hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
  var heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
  var bullet = /(?:[*+-]|\d{1,9}[.)])/;
  var lheadingCore = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/;
  var lheading = edit(lheadingCore).replace(/bull/g, bullet).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/\|table/g, "").getRegex();
  var lheadingGfm = edit(lheadingCore).replace(/bull/g, bullet).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex();
  var _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/;
  var blockText = /^[^\n]+/;
  var _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/;
  var def = edit(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", _blockLabel).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
  var list = edit(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g, bullet).getRegex();
  var _tag = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul";
  var _comment = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
  var html$2 = edit(
    "^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ 	]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ 	]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ 	]*)+\\n|$))",
    "i"
  ).replace("comment", _comment).replace("tag", _tag).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
  var paragraph = edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex();
  var blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", paragraph).getRegex();
  var blockNormal = {
    blockquote,
    code: blockCode,
    def,
    fences,
    heading,
    hr,
    html: html$2,
    lheading,
    list,
    newline,
    paragraph,
    table: noopTest,
    text: blockText
  };
  var gfmTable = edit(
    "^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"
  ).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3}	)[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex();
  var blockGfm = {
    ...blockNormal,
    lheading: lheadingGfm,
    table: gfmTable,
    paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", gfmTable).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex()
  };
  var blockPedantic = {
    ...blockNormal,
    html: edit(
      `^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`
    ).replace("comment", _comment).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),
    def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
    heading: /^(#{1,6})(.*)(?:\n+|$)/,
    fences: noopTest,
    // fences not supported
    lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,
    paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " *#{1,6} *[^\n]").replace("lheading", lheading).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex()
  };
  var escape = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
  var inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
  var br = /^( {2,}|\\)\n(?!\s*$)/;
  var inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
  var _punctuation = /[\p{P}\p{S}]/u;
  var _punctuationOrSpace = /[\s\p{P}\p{S}]/u;
  var _notPunctuationOrSpace = /[^\s\p{P}\p{S}]/u;
  var punctuation = edit(/^((?![*_])punctSpace)/, "u").replace(/punctSpace/g, _punctuationOrSpace).getRegex();
  var _punctuationGfmStrongEm = /(?!~)[\p{P}\p{S}]/u;
  var _punctuationOrSpaceGfmStrongEm = /(?!~)[\s\p{P}\p{S}]/u;
  var _notPunctuationOrSpaceGfmStrongEm = /(?:[^\s\p{P}\p{S}]|~)/u;
  var blockSkip = /\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g;
  var emStrongLDelimCore = /^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/;
  var emStrongLDelim = edit(emStrongLDelimCore, "u").replace(/punct/g, _punctuation).getRegex();
  var emStrongLDelimGfm = edit(emStrongLDelimCore, "u").replace(/punct/g, _punctuationGfmStrongEm).getRegex();
  var emStrongRDelimAstCore = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)";
  var emStrongRDelimAst = edit(emStrongRDelimAstCore, "gu").replace(/notPunctSpace/g, _notPunctuationOrSpace).replace(/punctSpace/g, _punctuationOrSpace).replace(/punct/g, _punctuation).getRegex();
  var emStrongRDelimAstGfm = edit(emStrongRDelimAstCore, "gu").replace(/notPunctSpace/g, _notPunctuationOrSpaceGfmStrongEm).replace(/punctSpace/g, _punctuationOrSpaceGfmStrongEm).replace(/punct/g, _punctuationGfmStrongEm).getRegex();
  var emStrongRDelimUnd = edit(
    "^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)",
    "gu"
  ).replace(/notPunctSpace/g, _notPunctuationOrSpace).replace(/punctSpace/g, _punctuationOrSpace).replace(/punct/g, _punctuation).getRegex();
  var anyPunctuation = edit(/\\(punct)/, "gu").replace(/punct/g, _punctuation).getRegex();
  var autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex();
  var _inlineComment = edit(_comment).replace("(?:-->|$)", "-->").getRegex();
  var tag = edit(
    "^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
  ).replace("comment", _inlineComment).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex();
  var _inlineLabel = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/;
  var link = edit(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label", _inlineLabel).replace("href", /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex();
  var reflink = edit(/^!?\[(label)\]\[(ref)\]/).replace("label", _inlineLabel).replace("ref", _blockLabel).getRegex();
  var nolink = edit(/^!?\[(ref)\](?:\[\])?/).replace("ref", _blockLabel).getRegex();
  var reflinkSearch = edit("reflink|nolink(?!\\()", "g").replace("reflink", reflink).replace("nolink", nolink).getRegex();
  var inlineNormal = {
    _backpedal: noopTest,
    // only used for GFM url
    anyPunctuation,
    autolink,
    blockSkip,
    br,
    code: inlineCode,
    del: noopTest,
    emStrongLDelim,
    emStrongRDelimAst,
    emStrongRDelimUnd,
    escape,
    link,
    nolink,
    punctuation,
    reflink,
    reflinkSearch,
    tag,
    text: inlineText,
    url: noopTest
  };
  var inlinePedantic = {
    ...inlineNormal,
    link: edit(/^!?\[(label)\]\((.*?)\)/).replace("label", _inlineLabel).getRegex(),
    reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", _inlineLabel).getRegex()
  };
  var inlineGfm = {
    ...inlineNormal,
    emStrongRDelimAst: emStrongRDelimAstGfm,
    emStrongLDelim: emStrongLDelimGfm,
    url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, "i").replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),
    _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,
    del: /^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,
    text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/
  };
  var inlineBreaks = {
    ...inlineGfm,
    br: edit(br).replace("{2,}", "*").getRegex(),
    text: edit(inlineGfm.text).replace("\\b_", "\\b_| {2,}\\n").replace(/\{2,\}/g, "*").getRegex()
  };
  var block = {
    normal: blockNormal,
    gfm: blockGfm,
    pedantic: blockPedantic
  };
  var inline = {
    normal: inlineNormal,
    gfm: inlineGfm,
    breaks: inlineBreaks,
    pedantic: inlinePedantic
  };

  // src/helpers.ts
  var escapeReplacements = {
    "&": "&amp;",
    "<": "&lt;",
    ">": "&gt;",
    '"': "&quot;",
    "'": "&#39;"
  };
  var getEscapeReplacement = (ch) => escapeReplacements[ch];
  function escape2(html2, encode) {
    if (encode) {
      if (other.escapeTest.test(html2)) {
        return html2.replace(other.escapeReplace, getEscapeReplacement);
      }
    } else {
      if (other.escapeTestNoEncode.test(html2)) {
        return html2.replace(other.escapeReplaceNoEncode, getEscapeReplacement);
      }
    }
    return html2;
  }
  function cleanUrl(href) {
    try {
      href = encodeURI(href).replace(other.percentDecode, "%");
    } catch {
      return null;
    }
    return href;
  }
  function splitCells(tableRow, count) {
    const row = tableRow.replace(other.findPipe, (match, offset, str) => {
      let escaped = false;
      let curr = offset;
      while (--curr >= 0 && str[curr] === "\\") escaped = !escaped;
      if (escaped) {
        return "|";
      } else {
        return " |";
      }
    }), cells = row.split(other.splitPipe);
    let i = 0;
    if (!cells[0].trim()) {
      cells.shift();
    }
    if (cells.length > 0 && !cells.at(-1)?.trim()) {
      cells.pop();
    }
    if (count) {
      if (cells.length > count) {
        cells.splice(count);
      } else {
        while (cells.length < count) cells.push("");
      }
    }
    for (; i < cells.length; i++) {
      cells[i] = cells[i].trim().replace(other.slashPipe, "|");
    }
    return cells;
  }
  function rtrim(str, c, invert) {
    const l = str.length;
    if (l === 0) {
      return "";
    }
    let suffLen = 0;
    while (suffLen < l) {
      const currChar = str.charAt(l - suffLen - 1);
      if (currChar === c && true) {
        suffLen++;
      } else {
        break;
      }
    }
    return str.slice(0, l - suffLen);
  }
  function findClosingBracket(str, b) {
    if (str.indexOf(b[1]) === -1) {
      return -1;
    }
    let level = 0;
    for (let i = 0; i < str.length; i++) {
      if (str[i] === "\\") {
        i++;
      } else if (str[i] === b[0]) {
        level++;
      } else if (str[i] === b[1]) {
        level--;
        if (level < 0) {
          return i;
        }
      }
    }
    if (level > 0) {
      return -2;
    }
    return -1;
  }

  // src/Tokenizer.ts
  function outputLink(cap, link2, raw, lexer2, rules) {
    const href = link2.href;
    const title = link2.title || null;
    const text = cap[1].replace(rules.other.outputLinkReplace, "$1");
    lexer2.state.inLink = true;
    const token = {
      type: cap[0].charAt(0) === "!" ? "image" : "link",
      raw,
      href,
      title,
      text,
      tokens: lexer2.inlineTokens(text)
    };
    lexer2.state.inLink = false;
    return token;
  }
  function indentCodeCompensation(raw, text, rules) {
    const matchIndentToCode = raw.match(rules.other.indentCodeCompensation);
    if (matchIndentToCode === null) {
      return text;
    }
    const indentToCode = matchIndentToCode[1];
    return text.split("\n").map((node) => {
      const matchIndentInNode = node.match(rules.other.beginningSpace);
      if (matchIndentInNode === null) {
        return node;
      }
      const [indentInNode] = matchIndentInNode;
      if (indentInNode.length >= indentToCode.length) {
        return node.slice(indentToCode.length);
      }
      return node;
    }).join("\n");
  }
  var _Tokenizer = class {
    options;
    rules;
    // set by the lexer
    lexer;
    // set by the lexer
    constructor(options2) {
      this.options = options2 || _defaults;
    }
    space(src) {
      const cap = this.rules.block.newline.exec(src);
      if (cap && cap[0].length > 0) {
        return {
          type: "space",
          raw: cap[0]
        };
      }
    }
    code(src) {
      const cap = this.rules.block.code.exec(src);
      if (cap) {
        const text = cap[0].replace(this.rules.other.codeRemoveIndent, "");
        return {
          type: "code",
          raw: cap[0],
          codeBlockStyle: "indented",
          text: !this.options.pedantic ? rtrim(text, "\n") : text
        };
      }
    }
    fences(src) {
      const cap = this.rules.block.fences.exec(src);
      if (cap) {
        const raw = cap[0];
        const text = indentCodeCompensation(raw, cap[3] || "", this.rules);
        return {
          type: "code",
          raw,
          lang: cap[2] ? cap[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : cap[2],
          text
        };
      }
    }
    heading(src) {
      const cap = this.rules.block.heading.exec(src);
      if (cap) {
        let text = cap[2].trim();
        if (this.rules.other.endingHash.test(text)) {
          const trimmed = rtrim(text, "#");
          if (this.options.pedantic) {
            text = trimmed.trim();
          } else if (!trimmed || this.rules.other.endingSpaceChar.test(trimmed)) {
            text = trimmed.trim();
          }
        }
        return {
          type: "heading",
          raw: cap[0],
          depth: cap[1].length,
          text,
          tokens: this.lexer.inline(text)
        };
      }
    }
    hr(src) {
      const cap = this.rules.block.hr.exec(src);
      if (cap) {
        return {
          type: "hr",
          raw: rtrim(cap[0], "\n")
        };
      }
    }
    blockquote(src) {
      const cap = this.rules.block.blockquote.exec(src);
      if (cap) {
        let lines = rtrim(cap[0], "\n").split("\n");
        let raw = "";
        let text = "";
        const tokens = [];
        while (lines.length > 0) {
          let inBlockquote = false;
          const currentLines = [];
          let i;
          for (i = 0; i < lines.length; i++) {
            if (this.rules.other.blockquoteStart.test(lines[i])) {
              currentLines.push(lines[i]);
              inBlockquote = true;
            } else if (!inBlockquote) {
              currentLines.push(lines[i]);
            } else {
              break;
            }
          }
          lines = lines.slice(i);
          const currentRaw = currentLines.join("\n");
          const currentText = currentRaw.replace(this.rules.other.blockquoteSetextReplace, "\n    $1").replace(this.rules.other.blockquoteSetextReplace2, "");
          raw = raw ? `${raw}
${currentRaw}` : currentRaw;
          text = text ? `${text}
${currentText}` : currentText;
          const top = this.lexer.state.top;
          this.lexer.state.top = true;
          this.lexer.blockTokens(currentText, tokens, true);
          this.lexer.state.top = top;
          if (lines.length === 0) {
            break;
          }
          const lastToken = tokens.at(-1);
          if (lastToken?.type === "code") {
            break;
          } else if (lastToken?.type === "blockquote") {
            const oldToken = lastToken;
            const newText = oldToken.raw + "\n" + lines.join("\n");
            const newToken = this.blockquote(newText);
            tokens[tokens.length - 1] = newToken;
            raw = raw.substring(0, raw.length - oldToken.raw.length) + newToken.raw;
            text = text.substring(0, text.length - oldToken.text.length) + newToken.text;
            break;
          } else if (lastToken?.type === "list") {
            const oldToken = lastToken;
            const newText = oldToken.raw + "\n" + lines.join("\n");
            const newToken = this.list(newText);
            tokens[tokens.length - 1] = newToken;
            raw = raw.substring(0, raw.length - lastToken.raw.length) + newToken.raw;
            text = text.substring(0, text.length - oldToken.raw.length) + newToken.raw;
            lines = newText.substring(tokens.at(-1).raw.length).split("\n");
            continue;
          }
        }
        return {
          type: "blockquote",
          raw,
          tokens,
          text
        };
      }
    }
    list(src) {
      let cap = this.rules.block.list.exec(src);
      if (cap) {
        let bull = cap[1].trim();
        const isordered = bull.length > 1;
        const list2 = {
          type: "list",
          raw: "",
          ordered: isordered,
          start: isordered ? +bull.slice(0, -1) : "",
          loose: false,
          items: []
        };
        bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`;
        if (this.options.pedantic) {
          bull = isordered ? bull : "[*+-]";
        }
        const itemRegex = this.rules.other.listItemRegex(bull);
        let endsWithBlankLine = false;
        while (src) {
          let endEarly = false;
          let raw = "";
          let itemContents = "";
          if (!(cap = itemRegex.exec(src))) {
            break;
          }
          if (this.rules.block.hr.test(src)) {
            break;
          }
          raw = cap[0];
          src = src.substring(raw.length);
          let line = cap[2].split("\n", 1)[0].replace(this.rules.other.listReplaceTabs, (t) => " ".repeat(3 * t.length));
          let nextLine = src.split("\n", 1)[0];
          let blankLine = !line.trim();
          let indent = 0;
          if (this.options.pedantic) {
            indent = 2;
            itemContents = line.trimStart();
          } else if (blankLine) {
            indent = cap[1].length + 1;
          } else {
            indent = cap[2].search(this.rules.other.nonSpaceChar);
            indent = indent > 4 ? 1 : indent;
            itemContents = line.slice(indent);
            indent += cap[1].length;
          }
          if (blankLine && this.rules.other.blankLine.test(nextLine)) {
            raw += nextLine + "\n";
            src = src.substring(nextLine.length + 1);
            endEarly = true;
          }
          if (!endEarly) {
            const nextBulletRegex = this.rules.other.nextBulletRegex(indent);
            const hrRegex = this.rules.other.hrRegex(indent);
            const fencesBeginRegex = this.rules.other.fencesBeginRegex(indent);
            const headingBeginRegex = this.rules.other.headingBeginRegex(indent);
            const htmlBeginRegex = this.rules.other.htmlBeginRegex(indent);
            while (src) {
              const rawLine = src.split("\n", 1)[0];
              let nextLineWithoutTabs;
              nextLine = rawLine;
              if (this.options.pedantic) {
                nextLine = nextLine.replace(this.rules.other.listReplaceNesting, "  ");
                nextLineWithoutTabs = nextLine;
              } else {
                nextLineWithoutTabs = nextLine.replace(this.rules.other.tabCharGlobal, "    ");
              }
              if (fencesBeginRegex.test(nextLine)) {
                break;
              }
              if (headingBeginRegex.test(nextLine)) {
                break;
              }
              if (htmlBeginRegex.test(nextLine)) {
                break;
              }
              if (nextBulletRegex.test(nextLine)) {
                break;
              }
              if (hrRegex.test(nextLine)) {
                break;
              }
              if (nextLineWithoutTabs.search(this.rules.other.nonSpaceChar) >= indent || !nextLine.trim()) {
                itemContents += "\n" + nextLineWithoutTabs.slice(indent);
              } else {
                if (blankLine) {
                  break;
                }
                if (line.replace(this.rules.other.tabCharGlobal, "    ").search(this.rules.other.nonSpaceChar) >= 4) {
                  break;
                }
                if (fencesBeginRegex.test(line)) {
                  break;
                }
                if (headingBeginRegex.test(line)) {
                  break;
                }
                if (hrRegex.test(line)) {
                  break;
                }
                itemContents += "\n" + nextLine;
              }
              if (!blankLine && !nextLine.trim()) {
                blankLine = true;
              }
              raw += rawLine + "\n";
              src = src.substring(rawLine.length + 1);
              line = nextLineWithoutTabs.slice(indent);
            }
          }
          if (!list2.loose) {
            if (endsWithBlankLine) {
              list2.loose = true;
            } else if (this.rules.other.doubleBlankLine.test(raw)) {
              endsWithBlankLine = true;
            }
          }
          let istask = null;
          let ischecked;
          if (this.options.gfm) {
            istask = this.rules.other.listIsTask.exec(itemContents);
            if (istask) {
              ischecked = istask[0] !== "[ ] ";
              itemContents = itemContents.replace(this.rules.other.listReplaceTask, "");
            }
          }
          list2.items.push({
            type: "list_item",
            raw,
            task: !!istask,
            checked: ischecked,
            loose: false,
            text: itemContents,
            tokens: []
          });
          list2.raw += raw;
        }
        const lastItem = list2.items.at(-1);
        if (lastItem) {
          lastItem.raw = lastItem.raw.trimEnd();
          lastItem.text = lastItem.text.trimEnd();
        } else {
          return;
        }
        list2.raw = list2.raw.trimEnd();
        for (let i = 0; i < list2.items.length; i++) {
          this.lexer.state.top = false;
          list2.items[i].tokens = this.lexer.blockTokens(list2.items[i].text, []);
          if (!list2.loose) {
            const spacers = list2.items[i].tokens.filter((t) => t.type === "space");
            const hasMultipleLineBreaks = spacers.length > 0 && spacers.some((t) => this.rules.other.anyLine.test(t.raw));
            list2.loose = hasMultipleLineBreaks;
          }
        }
        if (list2.loose) {
          for (let i = 0; i < list2.items.length; i++) {
            list2.items[i].loose = true;
          }
        }
        return list2;
      }
    }
    html(src) {
      const cap = this.rules.block.html.exec(src);
      if (cap) {
        const token = {
          type: "html",
          block: true,
          raw: cap[0],
          pre: cap[1] === "pre" || cap[1] === "script" || cap[1] === "style",
          text: cap[0]
        };
        return token;
      }
    }
    def(src) {
      const cap = this.rules.block.def.exec(src);
      if (cap) {
        const tag2 = cap[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal, " ");
        const href = cap[2] ? cap[2].replace(this.rules.other.hrefBrackets, "$1").replace(this.rules.inline.anyPunctuation, "$1") : "";
        const title = cap[3] ? cap[3].substring(1, cap[3].length - 1).replace(this.rules.inline.anyPunctuation, "$1") : cap[3];
        return {
          type: "def",
          tag: tag2,
          raw: cap[0],
          href,
          title
        };
      }
    }
    table(src) {
      const cap = this.rules.block.table.exec(src);
      if (!cap) {
        return;
      }
      if (!this.rules.other.tableDelimiter.test(cap[2])) {
        return;
      }
      const headers = splitCells(cap[1]);
      const aligns = cap[2].replace(this.rules.other.tableAlignChars, "").split("|");
      const rows = cap[3]?.trim() ? cap[3].replace(this.rules.other.tableRowBlankLine, "").split("\n") : [];
      const item = {
        type: "table",
        raw: cap[0],
        header: [],
        align: [],
        rows: []
      };
      if (headers.length !== aligns.length) {
        return;
      }
      for (const align of aligns) {
        if (this.rules.other.tableAlignRight.test(align)) {
          item.align.push("right");
        } else if (this.rules.other.tableAlignCenter.test(align)) {
          item.align.push("center");
        } else if (this.rules.other.tableAlignLeft.test(align)) {
          item.align.push("left");
        } else {
          item.align.push(null);
        }
      }
      for (let i = 0; i < headers.length; i++) {
        item.header.push({
          text: headers[i],
          tokens: this.lexer.inline(headers[i]),
          header: true,
          align: item.align[i]
        });
      }
      for (const row of rows) {
        item.rows.push(splitCells(row, item.header.length).map((cell, i) => {
          return {
            text: cell,
            tokens: this.lexer.inline(cell),
            header: false,
            align: item.align[i]
          };
        }));
      }
      return item;
    }
    lheading(src) {
      const cap = this.rules.block.lheading.exec(src);
      if (cap) {
        return {
          type: "heading",
          raw: cap[0],
          depth: cap[2].charAt(0) === "=" ? 1 : 2,
          text: cap[1],
          tokens: this.lexer.inline(cap[1])
        };
      }
    }
    paragraph(src) {
      const cap = this.rules.block.paragraph.exec(src);
      if (cap) {
        const text = cap[1].charAt(cap[1].length - 1) === "\n" ? cap[1].slice(0, -1) : cap[1];
        return {
          type: "paragraph",
          raw: cap[0],
          text,
          tokens: this.lexer.inline(text)
        };
      }
    }
    text(src) {
      const cap = this.rules.block.text.exec(src);
      if (cap) {
        return {
          type: "text",
          raw: cap[0],
          text: cap[0],
          tokens: this.lexer.inline(cap[0])
        };
      }
    }
    escape(src) {
      const cap = this.rules.inline.escape.exec(src);
      if (cap) {
        return {
          type: "escape",
          raw: cap[0],
          text: cap[1]
        };
      }
    }
    tag(src) {
      const cap = this.rules.inline.tag.exec(src);
      if (cap) {
        if (!this.lexer.state.inLink && this.rules.other.startATag.test(cap[0])) {
          this.lexer.state.inLink = true;
        } else if (this.lexer.state.inLink && this.rules.other.endATag.test(cap[0])) {
          this.lexer.state.inLink = false;
        }
        if (!this.lexer.state.inRawBlock && this.rules.other.startPreScriptTag.test(cap[0])) {
          this.lexer.state.inRawBlock = true;
        } else if (this.lexer.state.inRawBlock && this.rules.other.endPreScriptTag.test(cap[0])) {
          this.lexer.state.inRawBlock = false;
        }
        return {
          type: "html",
          raw: cap[0],
          inLink: this.lexer.state.inLink,
          inRawBlock: this.lexer.state.inRawBlock,
          block: false,
          text: cap[0]
        };
      }
    }
    link(src) {
      const cap = this.rules.inline.link.exec(src);
      if (cap) {
        const trimmedUrl = cap[2].trim();
        if (!this.options.pedantic && this.rules.other.startAngleBracket.test(trimmedUrl)) {
          if (!this.rules.other.endAngleBracket.test(trimmedUrl)) {
            return;
          }
          const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), "\\");
          if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
            return;
          }
        } else {
          const lastParenIndex = findClosingBracket(cap[2], "()");
          if (lastParenIndex === -2) {
            return;
          }
          if (lastParenIndex > -1) {
            const start = cap[0].indexOf("!") === 0 ? 5 : 4;
            const linkLen = start + cap[1].length + lastParenIndex;
            cap[2] = cap[2].substring(0, lastParenIndex);
            cap[0] = cap[0].substring(0, linkLen).trim();
            cap[3] = "";
          }
        }
        let href = cap[2];
        let title = "";
        if (this.options.pedantic) {
          const link2 = this.rules.other.pedanticHrefTitle.exec(href);
          if (link2) {
            href = link2[1];
            title = link2[3];
          }
        } else {
          title = cap[3] ? cap[3].slice(1, -1) : "";
        }
        href = href.trim();
        if (this.rules.other.startAngleBracket.test(href)) {
          if (this.options.pedantic && !this.rules.other.endAngleBracket.test(trimmedUrl)) {
            href = href.slice(1);
          } else {
            href = href.slice(1, -1);
          }
        }
        return outputLink(cap, {
          href: href ? href.replace(this.rules.inline.anyPunctuation, "$1") : href,
          title: title ? title.replace(this.rules.inline.anyPunctuation, "$1") : title
        }, cap[0], this.lexer, this.rules);
      }
    }
    reflink(src, links) {
      let cap;
      if ((cap = this.rules.inline.reflink.exec(src)) || (cap = this.rules.inline.nolink.exec(src))) {
        const linkString = (cap[2] || cap[1]).replace(this.rules.other.multipleSpaceGlobal, " ");
        const link2 = links[linkString.toLowerCase()];
        if (!link2) {
          const text = cap[0].charAt(0);
          return {
            type: "text",
            raw: text,
            text
          };
        }
        return outputLink(cap, link2, cap[0], this.lexer, this.rules);
      }
    }
    emStrong(src, maskedSrc, prevChar = "") {
      let match = this.rules.inline.emStrongLDelim.exec(src);
      if (!match) return;
      if (match[3] && prevChar.match(this.rules.other.unicodeAlphaNumeric)) return;
      const nextChar = match[1] || match[2] || "";
      if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) {
        const lLength = [...match[0]].length - 1;
        let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0;
        const endReg = match[0][0] === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
        endReg.lastIndex = 0;
        maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
        while ((match = endReg.exec(maskedSrc)) != null) {
          rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
          if (!rDelim) continue;
          rLength = [...rDelim].length;
          if (match[3] || match[4]) {
            delimTotal += rLength;
            continue;
          } else if (match[5] || match[6]) {
            if (lLength % 3 && !((lLength + rLength) % 3)) {
              midDelimTotal += rLength;
              continue;
            }
          }
          delimTotal -= rLength;
          if (delimTotal > 0) continue;
          rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
          const lastCharLength = [...match[0]][0].length;
          const raw = src.slice(0, lLength + match.index + lastCharLength + rLength);
          if (Math.min(lLength, rLength) % 2) {
            const text2 = raw.slice(1, -1);
            return {
              type: "em",
              raw,
              text: text2,
              tokens: this.lexer.inlineTokens(text2)
            };
          }
          const text = raw.slice(2, -2);
          return {
            type: "strong",
            raw,
            text,
            tokens: this.lexer.inlineTokens(text)
          };
        }
      }
    }
    codespan(src) {
      const cap = this.rules.inline.code.exec(src);
      if (cap) {
        let text = cap[2].replace(this.rules.other.newLineCharGlobal, " ");
        const hasNonSpaceChars = this.rules.other.nonSpaceChar.test(text);
        const hasSpaceCharsOnBothEnds = this.rules.other.startingSpaceChar.test(text) && this.rules.other.endingSpaceChar.test(text);
        if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
          text = text.substring(1, text.length - 1);
        }
        return {
          type: "codespan",
          raw: cap[0],
          text
        };
      }
    }
    br(src) {
      const cap = this.rules.inline.br.exec(src);
      if (cap) {
        return {
          type: "br",
          raw: cap[0]
        };
      }
    }
    del(src) {
      const cap = this.rules.inline.del.exec(src);
      if (cap) {
        return {
          type: "del",
          raw: cap[0],
          text: cap[2],
          tokens: this.lexer.inlineTokens(cap[2])
        };
      }
    }
    autolink(src) {
      const cap = this.rules.inline.autolink.exec(src);
      if (cap) {
        let text, href;
        if (cap[2] === "@") {
          text = cap[1];
          href = "mailto:" + text;
        } else {
          text = cap[1];
          href = text;
        }
        return {
          type: "link",
          raw: cap[0],
          text,
          href,
          tokens: [
            {
              type: "text",
              raw: text,
              text
            }
          ]
        };
      }
    }
    url(src) {
      let cap;
      if (cap = this.rules.inline.url.exec(src)) {
        let text, href;
        if (cap[2] === "@") {
          text = cap[0];
          href = "mailto:" + text;
        } else {
          let prevCapZero;
          do {
            prevCapZero = cap[0];
            cap[0] = this.rules.inline._backpedal.exec(cap[0])?.[0] ?? "";
          } while (prevCapZero !== cap[0]);
          text = cap[0];
          if (cap[1] === "www.") {
            href = "http://" + cap[0];
          } else {
            href = cap[0];
          }
        }
        return {
          type: "link",
          raw: cap[0],
          text,
          href,
          tokens: [
            {
              type: "text",
              raw: text,
              text
            }
          ]
        };
      }
    }
    inlineText(src) {
      const cap = this.rules.inline.text.exec(src);
      if (cap) {
        const escaped = this.lexer.state.inRawBlock;
        return {
          type: "text",
          raw: cap[0],
          text: cap[0],
          escaped
        };
      }
    }
  };

  // src/Lexer.ts
  var _Lexer = class __Lexer {
    tokens;
    options;
    state;
    tokenizer;
    inlineQueue;
    constructor(options2) {
      this.tokens = [];
      this.tokens.links = /* @__PURE__ */ Object.create(null);
      this.options = options2 || _defaults;
      this.options.tokenizer = this.options.tokenizer || new _Tokenizer();
      this.tokenizer = this.options.tokenizer;
      this.tokenizer.options = this.options;
      this.tokenizer.lexer = this;
      this.inlineQueue = [];
      this.state = {
        inLink: false,
        inRawBlock: false,
        top: true
      };
      const rules = {
        other,
        block: block.normal,
        inline: inline.normal
      };
      if (this.options.pedantic) {
        rules.block = block.pedantic;
        rules.inline = inline.pedantic;
      } else if (this.options.gfm) {
        rules.block = block.gfm;
        if (this.options.breaks) {
          rules.inline = inline.breaks;
        } else {
          rules.inline = inline.gfm;
        }
      }
      this.tokenizer.rules = rules;
    }
    /**
     * Expose Rules
     */
    static get rules() {
      return {
        block,
        inline
      };
    }
    /**
     * Static Lex Method
     */
    static lex(src, options2) {
      const lexer2 = new __Lexer(options2);
      return lexer2.lex(src);
    }
    /**
     * Static Lex Inline Method
     */
    static lexInline(src, options2) {
      const lexer2 = new __Lexer(options2);
      return lexer2.inlineTokens(src);
    }
    /**
     * Preprocessing
     */
    lex(src) {
      src = src.replace(other.carriageReturn, "\n");
      this.blockTokens(src, this.tokens);
      for (let i = 0; i < this.inlineQueue.length; i++) {
        const next = this.inlineQueue[i];
        this.inlineTokens(next.src, next.tokens);
      }
      this.inlineQueue = [];
      return this.tokens;
    }
    blockTokens(src, tokens = [], lastParagraphClipped = false) {
      if (this.options.pedantic) {
        src = src.replace(other.tabCharGlobal, "    ").replace(other.spaceLine, "");
      }
      while (src) {
        let token;
        if (this.options.extensions?.block?.some((extTokenizer) => {
          if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
            src = src.substring(token.raw.length);
            tokens.push(token);
            return true;
          }
          return false;
        })) {
          continue;
        }
        if (token = this.tokenizer.space(src)) {
          src = src.substring(token.raw.length);
          const lastToken = tokens.at(-1);
          if (token.raw.length === 1 && lastToken !== void 0) {
            lastToken.raw += "\n";
          } else {
            tokens.push(token);
          }
          continue;
        }
        if (token = this.tokenizer.code(src)) {
          src = src.substring(token.raw.length);
          const lastToken = tokens.at(-1);
          if (lastToken?.type === "paragraph" || lastToken?.type === "text") {
            lastToken.raw += "\n" + token.raw;
            lastToken.text += "\n" + token.text;
            this.inlineQueue.at(-1).src = lastToken.text;
          } else {
            tokens.push(token);
          }
          continue;
        }
        if (token = this.tokenizer.fences(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.heading(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.hr(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.blockquote(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.list(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.html(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.def(src)) {
          src = src.substring(token.raw.length);
          const lastToken = tokens.at(-1);
          if (lastToken?.type === "paragraph" || lastToken?.type === "text") {
            lastToken.raw += "\n" + token.raw;
            lastToken.text += "\n" + token.raw;
            this.inlineQueue.at(-1).src = lastToken.text;
          } else if (!this.tokens.links[token.tag]) {
            this.tokens.links[token.tag] = {
              href: token.href,
              title: token.title
            };
          }
          continue;
        }
        if (token = this.tokenizer.table(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.lheading(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        let cutSrc = src;
        if (this.options.extensions?.startBlock) {
          let startIndex = Infinity;
          const tempSrc = src.slice(1);
          let tempStart;
          this.options.extensions.startBlock.forEach((getStartIndex) => {
            tempStart = getStartIndex.call({ lexer: this }, tempSrc);
            if (typeof tempStart === "number" && tempStart >= 0) {
              startIndex = Math.min(startIndex, tempStart);
            }
          });
          if (startIndex < Infinity && startIndex >= 0) {
            cutSrc = src.substring(0, startIndex + 1);
          }
        }
        if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {
          const lastToken = tokens.at(-1);
          if (lastParagraphClipped && lastToken?.type === "paragraph") {
            lastToken.raw += "\n" + token.raw;
            lastToken.text += "\n" + token.text;
            this.inlineQueue.pop();
            this.inlineQueue.at(-1).src = lastToken.text;
          } else {
            tokens.push(token);
          }
          lastParagraphClipped = cutSrc.length !== src.length;
          src = src.substring(token.raw.length);
          continue;
        }
        if (token = this.tokenizer.text(src)) {
          src = src.substring(token.raw.length);
          const lastToken = tokens.at(-1);
          if (lastToken?.type === "text") {
            lastToken.raw += "\n" + token.raw;
            lastToken.text += "\n" + token.text;
            this.inlineQueue.pop();
            this.inlineQueue.at(-1).src = lastToken.text;
          } else {
            tokens.push(token);
          }
          continue;
        }
        if (src) {
          const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
          if (this.options.silent) {
            console.error(errMsg);
            break;
          } else {
            throw new Error(errMsg);
          }
        }
      }
      this.state.top = true;
      return tokens;
    }
    inline(src, tokens = []) {
      this.inlineQueue.push({ src, tokens });
      return tokens;
    }
    /**
     * Lexing/Compiling
     */
    inlineTokens(src, tokens = []) {
      let maskedSrc = src;
      let match = null;
      if (this.tokens.links) {
        const links = Object.keys(this.tokens.links);
        if (links.length > 0) {
          while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) {
            if (links.includes(match[0].slice(match[0].lastIndexOf("[") + 1, -1))) {
              maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
            }
          }
        }
      }
      while ((match = this.tokenizer.rules.inline.anyPunctuation.exec(maskedSrc)) != null) {
        maskedSrc = maskedSrc.slice(0, match.index) + "++" + maskedSrc.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);
      }
      while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) {
        maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
      }
      let keepPrevChar = false;
      let prevChar = "";
      while (src) {
        if (!keepPrevChar) {
          prevChar = "";
        }
        keepPrevChar = false;
        let token;
        if (this.options.extensions?.inline?.some((extTokenizer) => {
          if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
            src = src.substring(token.raw.length);
            tokens.push(token);
            return true;
          }
          return false;
        })) {
          continue;
        }
        if (token = this.tokenizer.escape(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.tag(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.link(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.reflink(src, this.tokens.links)) {
          src = src.substring(token.raw.length);
          const lastToken = tokens.at(-1);
          if (token.type === "text" && lastToken?.type === "text") {
            lastToken.raw += token.raw;
            lastToken.text += token.text;
          } else {
            tokens.push(token);
          }
          continue;
        }
        if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.codespan(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.br(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.del(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (token = this.tokenizer.autolink(src)) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        if (!this.state.inLink && (token = this.tokenizer.url(src))) {
          src = src.substring(token.raw.length);
          tokens.push(token);
          continue;
        }
        let cutSrc = src;
        if (this.options.extensions?.startInline) {
          let startIndex = Infinity;
          const tempSrc = src.slice(1);
          let tempStart;
          this.options.extensions.startInline.forEach((getStartIndex) => {
            tempStart = getStartIndex.call({ lexer: this }, tempSrc);
            if (typeof tempStart === "number" && tempStart >= 0) {
              startIndex = Math.min(startIndex, tempStart);
            }
          });
          if (startIndex < Infinity && startIndex >= 0) {
            cutSrc = src.substring(0, startIndex + 1);
          }
        }
        if (token = this.tokenizer.inlineText(cutSrc)) {
          src = src.substring(token.raw.length);
          if (token.raw.slice(-1) !== "_") {
            prevChar = token.raw.slice(-1);
          }
          keepPrevChar = true;
          const lastToken = tokens.at(-1);
          if (lastToken?.type === "text") {
            lastToken.raw += token.raw;
            lastToken.text += token.text;
          } else {
            tokens.push(token);
          }
          continue;
        }
        if (src) {
          const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
          if (this.options.silent) {
            console.error(errMsg);
            break;
          } else {
            throw new Error(errMsg);
          }
        }
      }
      return tokens;
    }
  };

  // src/Renderer.ts
  var _Renderer = class {
    options;
    parser;
    // set by the parser
    constructor(options2) {
      this.options = options2 || _defaults;
    }
    space(token) {
      return "";
    }
    code({ text, lang, escaped }) {
      const langString = (lang || "").match(other.notSpaceStart)?.[0];
      const code = text.replace(other.endingNewline, "") + "\n";
      if (!langString) {
        return "<pre><code>" + (escaped ? code : escape2(code, true)) + "</code></pre>\n";
      }
      return '<pre><code class="language-' + escape2(langString) + '">' + (escaped ? code : escape2(code, true)) + "</code></pre>\n";
    }
    blockquote({ tokens }) {
      const body = this.parser.parse(tokens);
      return `<blockquote>
${body}</blockquote>
`;
    }
    html({ text }) {
      return text;
    }
    heading({ tokens, depth }) {
      return `<h${depth}>${this.parser.parseInline(tokens)}</h${depth}>
`;
    }
    hr(token) {
      return "<hr>\n";
    }
    list(token) {
      const ordered = token.ordered;
      const start = token.start;
      let body = "";
      for (let j = 0; j < token.items.length; j++) {
        const item = token.items[j];
        body += this.listitem(item);
      }
      const type = ordered ? "ol" : "ul";
      const startAttr = ordered && start !== 1 ? ' start="' + start + '"' : "";
      return "<" + type + startAttr + ">\n" + body + "</" + type + ">\n";
    }
    listitem(item) {
      let itemBody = "";
      if (item.task) {
        const checkbox = this.checkbox({ checked: !!item.checked });
        if (item.loose) {
          if (item.tokens[0]?.type === "paragraph") {
            item.tokens[0].text = checkbox + " " + item.tokens[0].text;
            if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === "text") {
              item.tokens[0].tokens[0].text = checkbox + " " + escape2(item.tokens[0].tokens[0].text);
              item.tokens[0].tokens[0].escaped = true;
            }
          } else {
            item.tokens.unshift({
              type: "text",
              raw: checkbox + " ",
              text: checkbox + " ",
              escaped: true
            });
          }
        } else {
          itemBody += checkbox + " ";
        }
      }
      itemBody += this.parser.parse(item.tokens, !!item.loose);
      return `<li>${itemBody}</li>
`;
    }
    checkbox({ checked }) {
      return "<input " + (checked ? 'checked="" ' : "") + 'disabled="" type="checkbox">';
    }
    paragraph({ tokens }) {
      return `<p>${this.parser.parseInline(tokens)}</p>
`;
    }
    table(token) {
      let header = "";
      let cell = "";
      for (let j = 0; j < token.header.length; j++) {
        cell += this.tablecell(token.header[j]);
      }
      header += this.tablerow({ text: cell });
      let body = "";
      for (let j = 0; j < token.rows.length; j++) {
        const row = token.rows[j];
        cell = "";
        for (let k = 0; k < row.length; k++) {
          cell += this.tablecell(row[k]);
        }
        body += this.tablerow({ text: cell });
      }
      if (body) body = `<tbody>${body}</tbody>`;
      return "<table>\n<thead>\n" + header + "</thead>\n" + body + "</table>\n";
    }
    tablerow({ text }) {
      return `<tr>
${text}</tr>
`;
    }
    tablecell(token) {
      const content = this.parser.parseInline(token.tokens);
      const type = token.header ? "th" : "td";
      const tag2 = token.align ? `<${type} align="${token.align}">` : `<${type}>`;
      return tag2 + content + `</${type}>
`;
    }
    /**
     * span level renderer
     */
    strong({ tokens }) {
      return `<strong>${this.parser.parseInline(tokens)}</strong>`;
    }
    em({ tokens }) {
      return `<em>${this.parser.parseInline(tokens)}</em>`;
    }
    codespan({ text }) {
      return `<code>${escape2(text, true)}</code>`;
    }
    br(token) {
      return "<br>";
    }
    del({ tokens }) {
      return `<del>${this.parser.parseInline(tokens)}</del>`;
    }
    link({ href, title, tokens }) {
      const text = this.parser.parseInline(tokens);
      const cleanHref = cleanUrl(href);
      if (cleanHref === null) {
        return text;
      }
      href = cleanHref;
      let out = '<a href="' + href + '"';
      if (title) {
        out += ' title="' + escape2(title) + '"';
      }
      out += ">" + text + "</a>";
      return out;
    }
    image({ href, title, text, tokens }) {
      if (tokens) {
        text = this.parser.parseInline(tokens, this.parser.textRenderer);
      }
      const cleanHref = cleanUrl(href);
      if (cleanHref === null) {
        return escape2(text);
      }
      href = cleanHref;
      let out = `<img src="${href}" alt="${text}"`;
      if (title) {
        out += ` title="${escape2(title)}"`;
      }
      out += ">";
      return out;
    }
    text(token) {
      return "tokens" in token && token.tokens ? this.parser.parseInline(token.tokens) : "escaped" in token && token.escaped ? token.text : escape2(token.text);
    }
  };

  // src/TextRenderer.ts
  var _TextRenderer = class {
    // no need for block level renderers
    strong({ text }) {
      return text;
    }
    em({ text }) {
      return text;
    }
    codespan({ text }) {
      return text;
    }
    del({ text }) {
      return text;
    }
    html({ text }) {
      return text;
    }
    text({ text }) {
      return text;
    }
    link({ text }) {
      return "" + text;
    }
    image({ text }) {
      return "" + text;
    }
    br() {
      return "";
    }
  };

  // src/Parser.ts
  var _Parser = class __Parser {
    options;
    renderer;
    textRenderer;
    constructor(options2) {
      this.options = options2 || _defaults;
      this.options.renderer = this.options.renderer || new _Renderer();
      this.renderer = this.options.renderer;
      this.renderer.options = this.options;
      this.renderer.parser = this;
      this.textRenderer = new _TextRenderer();
    }
    /**
     * Static Parse Method
     */
    static parse(tokens, options2) {
      const parser2 = new __Parser(options2);
      return parser2.parse(tokens);
    }
    /**
     * Static Parse Inline Method
     */
    static parseInline(tokens, options2) {
      const parser2 = new __Parser(options2);
      return parser2.parseInline(tokens);
    }
    /**
     * Parse Loop
     */
    parse(tokens, top = true) {
      let out = "";
      for (let i = 0; i < tokens.length; i++) {
        const anyToken = tokens[i];
        if (this.options.extensions?.renderers?.[anyToken.type]) {
          const genericToken = anyToken;
          const ret = this.options.extensions.renderers[genericToken.type].call({ parser: this }, genericToken);
          if (ret !== false || !["space", "hr", "heading", "code", "table", "blockquote", "list", "html", "paragraph", "text"].includes(genericToken.type)) {
            out += ret || "";
            continue;
          }
        }
        const token = anyToken;
        switch (token.type) {
          case "space": {
            out += this.renderer.space(token);
            continue;
          }
          case "hr": {
            out += this.renderer.hr(token);
            continue;
          }
          case "heading": {
            out += this.renderer.heading(token);
            continue;
          }
          case "code": {
            out += this.renderer.code(token);
            continue;
          }
          case "table": {
            out += this.renderer.table(token);
            continue;
          }
          case "blockquote": {
            out += this.renderer.blockquote(token);
            continue;
          }
          case "list": {
            out += this.renderer.list(token);
            continue;
          }
          case "html": {
            out += this.renderer.html(token);
            continue;
          }
          case "paragraph": {
            out += this.renderer.paragraph(token);
            continue;
          }
          case "text": {
            let textToken = token;
            let body = this.renderer.text(textToken);
            while (i + 1 < tokens.length && tokens[i + 1].type === "text") {
              textToken = tokens[++i];
              body += "\n" + this.renderer.text(textToken);
            }
            if (top) {
              out += this.renderer.paragraph({
                type: "paragraph",
                raw: body,
                text: body,
                tokens: [{ type: "text", raw: body, text: body, escaped: true }]
              });
            } else {
              out += body;
            }
            continue;
          }
          default: {
            const errMsg = 'Token with "' + token.type + '" type was not found.';
            if (this.options.silent) {
              console.error(errMsg);
              return "";
            } else {
              throw new Error(errMsg);
            }
          }
        }
      }
      return out;
    }
    /**
     * Parse Inline Tokens
     */
    parseInline(tokens, renderer = this.renderer) {
      let out = "";
      for (let i = 0; i < tokens.length; i++) {
        const anyToken = tokens[i];
        if (this.options.extensions?.renderers?.[anyToken.type]) {
          const ret = this.options.extensions.renderers[anyToken.type].call({ parser: this }, anyToken);
          if (ret !== false || !["escape", "html", "link", "image", "strong", "em", "codespan", "br", "del", "text"].includes(anyToken.type)) {
            out += ret || "";
            continue;
          }
        }
        const token = anyToken;
        switch (token.type) {
          case "escape": {
            out += renderer.text(token);
            break;
          }
          case "html": {
            out += renderer.html(token);
            break;
          }
          case "link": {
            out += renderer.link(token);
            break;
          }
          case "image": {
            out += renderer.image(token);
            break;
          }
          case "strong": {
            out += renderer.strong(token);
            break;
          }
          case "em": {
            out += renderer.em(token);
            break;
          }
          case "codespan": {
            out += renderer.codespan(token);
            break;
          }
          case "br": {
            out += renderer.br(token);
            break;
          }
          case "del": {
            out += renderer.del(token);
            break;
          }
          case "text": {
            out += renderer.text(token);
            break;
          }
          default: {
            const errMsg = 'Token with "' + token.type + '" type was not found.';
            if (this.options.silent) {
              console.error(errMsg);
              return "";
            } else {
              throw new Error(errMsg);
            }
          }
        }
      }
      return out;
    }
  };

  // src/Hooks.ts
  var _Hooks = class {
    options;
    block;
    constructor(options2) {
      this.options = options2 || _defaults;
    }
    static passThroughHooks = /* @__PURE__ */ new Set([
      "preprocess",
      "postprocess",
      "processAllTokens"
    ]);
    /**
     * Process markdown before marked
     */
    preprocess(markdown) {
      return markdown;
    }
    /**
     * Process HTML after marked is finished
     */
    postprocess(html2) {
      return html2;
    }
    /**
     * Process all tokens before walk tokens
     */
    processAllTokens(tokens) {
      return tokens;
    }
    /**
     * Provide function to tokenize markdown
     */
    provideLexer() {
      return this.block ? _Lexer.lex : _Lexer.lexInline;
    }
    /**
     * Provide function to parse tokens
     */
    provideParser() {
      return this.block ? _Parser.parse : _Parser.parseInline;
    }
  };

  // src/Instance.ts
  var Marked = class {
    defaults = _getDefaults();
    options = this.setOptions;
    parse = this.parseMarkdown(true);
    parseInline = this.parseMarkdown(false);
    Parser = _Parser;
    Renderer = _Renderer;
    TextRenderer = _TextRenderer;
    Lexer = _Lexer;
    Tokenizer = _Tokenizer;
    Hooks = _Hooks;
    constructor(...args) {
      this.use(...args);
    }
    /**
     * Run callback for every token
     */
    walkTokens(tokens, callback) {
      let values = [];
      for (const token of tokens) {
        values = values.concat(callback.call(this, token));
        switch (token.type) {
          case "table": {
            const tableToken = token;
            for (const cell of tableToken.header) {
              values = values.concat(this.walkTokens(cell.tokens, callback));
            }
            for (const row of tableToken.rows) {
              for (const cell of row) {
                values = values.concat(this.walkTokens(cell.tokens, callback));
              }
            }
            break;
          }
          case "list": {
            const listToken = token;
            values = values.concat(this.walkTokens(listToken.items, callback));
            break;
          }
          default: {
            const genericToken = token;
            if (this.defaults.extensions?.childTokens?.[genericToken.type]) {
              this.defaults.extensions.childTokens[genericToken.type].forEach((childTokens) => {
                const tokens2 = genericToken[childTokens].flat(Infinity);
                values = values.concat(this.walkTokens(tokens2, callback));
              });
            } else if (genericToken.tokens) {
              values = values.concat(this.walkTokens(genericToken.tokens, callback));
            }
          }
        }
      }
      return values;
    }
    use(...args) {
      const extensions = this.defaults.extensions || { renderers: {}, childTokens: {} };
      args.forEach((pack) => {
        const opts = { ...pack };
        opts.async = this.defaults.async || opts.async || false;
        if (pack.extensions) {
          pack.extensions.forEach((ext) => {
            if (!ext.name) {
              throw new Error("extension name required");
            }
            if ("renderer" in ext) {
              const prevRenderer = extensions.renderers[ext.name];
              if (prevRenderer) {
                extensions.renderers[ext.name] = function(...args2) {
                  let ret = ext.renderer.apply(this, args2);
                  if (ret === false) {
                    ret = prevRenderer.apply(this, args2);
                  }
                  return ret;
                };
              } else {
                extensions.renderers[ext.name] = ext.renderer;
              }
            }
            if ("tokenizer" in ext) {
              if (!ext.level || ext.level !== "block" && ext.level !== "inline") {
                throw new Error("extension level must be 'block' or 'inline'");
              }
              const extLevel = extensions[ext.level];
              if (extLevel) {
                extLevel.unshift(ext.tokenizer);
              } else {
                extensions[ext.level] = [ext.tokenizer];
              }
              if (ext.start) {
                if (ext.level === "block") {
                  if (extensions.startBlock) {
                    extensions.startBlock.push(ext.start);
                  } else {
                    extensions.startBlock = [ext.start];
                  }
                } else if (ext.level === "inline") {
                  if (extensions.startInline) {
                    extensions.startInline.push(ext.start);
                  } else {
                    extensions.startInline = [ext.start];
                  }
                }
              }
            }
            if ("childTokens" in ext && ext.childTokens) {
              extensions.childTokens[ext.name] = ext.childTokens;
            }
          });
          opts.extensions = extensions;
        }
        if (pack.renderer) {
          const renderer = this.defaults.renderer || new _Renderer(this.defaults);
          for (const prop in pack.renderer) {
            if (!(prop in renderer)) {
              throw new Error(`renderer '${prop}' does not exist`);
            }
            if (["options", "parser"].includes(prop)) {
              continue;
            }
            const rendererProp = prop;
            const rendererFunc = pack.renderer[rendererProp];
            const prevRenderer = renderer[rendererProp];
            renderer[rendererProp] = (...args2) => {
              let ret = rendererFunc.apply(renderer, args2);
              if (ret === false) {
                ret = prevRenderer.apply(renderer, args2);
              }
              return ret || "";
            };
          }
          opts.renderer = renderer;
        }
        if (pack.tokenizer) {
          const tokenizer = this.defaults.tokenizer || new _Tokenizer(this.defaults);
          for (const prop in pack.tokenizer) {
            if (!(prop in tokenizer)) {
              throw new Error(`tokenizer '${prop}' does not exist`);
            }
            if (["options", "rules", "lexer"].includes(prop)) {
              continue;
            }
            const tokenizerProp = prop;
            const tokenizerFunc = pack.tokenizer[tokenizerProp];
            const prevTokenizer = tokenizer[tokenizerProp];
            tokenizer[tokenizerProp] = (...args2) => {
              let ret = tokenizerFunc.apply(tokenizer, args2);
              if (ret === false) {
                ret = prevTokenizer.apply(tokenizer, args2);
              }
              return ret;
            };
          }
          opts.tokenizer = tokenizer;
        }
        if (pack.hooks) {
          const hooks = this.defaults.hooks || new _Hooks();
          for (const prop in pack.hooks) {
            if (!(prop in hooks)) {
              throw new Error(`hook '${prop}' does not exist`);
            }
            if (["options", "block"].includes(prop)) {
              continue;
            }
            const hooksProp = prop;
            const hooksFunc = pack.hooks[hooksProp];
            const prevHook = hooks[hooksProp];
            if (_Hooks.passThroughHooks.has(prop)) {
              hooks[hooksProp] = (arg) => {
                if (this.defaults.async) {
                  return Promise.resolve(hooksFunc.call(hooks, arg)).then((ret2) => {
                    return prevHook.call(hooks, ret2);
                  });
                }
                const ret = hooksFunc.call(hooks, arg);
                return prevHook.call(hooks, ret);
              };
            } else {
              hooks[hooksProp] = (...args2) => {
                let ret = hooksFunc.apply(hooks, args2);
                if (ret === false) {
                  ret = prevHook.apply(hooks, args2);
                }
                return ret;
              };
            }
          }
          opts.hooks = hooks;
        }
        if (pack.walkTokens) {
          const walkTokens2 = this.defaults.walkTokens;
          const packWalktokens = pack.walkTokens;
          opts.walkTokens = function(token) {
            let values = [];
            values.push(packWalktokens.call(this, token));
            if (walkTokens2) {
              values = values.concat(walkTokens2.call(this, token));
            }
            return values;
          };
        }
        this.defaults = { ...this.defaults, ...opts };
      });
      return this;
    }
    setOptions(opt) {
      this.defaults = { ...this.defaults, ...opt };
      return this;
    }
    lexer(src, options2) {
      return _Lexer.lex(src, options2 ?? this.defaults);
    }
    parser(tokens, options2) {
      return _Parser.parse(tokens, options2 ?? this.defaults);
    }
    parseMarkdown(blockType) {
      const parse2 = (src, options2) => {
        const origOpt = { ...options2 };
        const opt = { ...this.defaults, ...origOpt };
        const throwError = this.onError(!!opt.silent, !!opt.async);
        if (this.defaults.async === true && origOpt.async === false) {
          return throwError(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));
        }
        if (typeof src === "undefined" || src === null) {
          return throwError(new Error("marked(): input parameter is undefined or null"));
        }
        if (typeof src !== "string") {
          return throwError(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(src) + ", string expected"));
        }
        if (opt.hooks) {
          opt.hooks.options = opt;
          opt.hooks.block = blockType;
        }
        const lexer2 = opt.hooks ? opt.hooks.provideLexer() : blockType ? _Lexer.lex : _Lexer.lexInline;
        const parser2 = opt.hooks ? opt.hooks.provideParser() : blockType ? _Parser.parse : _Parser.parseInline;
        if (opt.async) {
          return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src).then((src2) => lexer2(src2, opt)).then((tokens) => opt.hooks ? opt.hooks.processAllTokens(tokens) : tokens).then((tokens) => opt.walkTokens ? Promise.all(this.walkTokens(tokens, opt.walkTokens)).then(() => tokens) : tokens).then((tokens) => parser2(tokens, opt)).then((html2) => opt.hooks ? opt.hooks.postprocess(html2) : html2).catch(throwError);
        }
        try {
          if (opt.hooks) {
            src = opt.hooks.preprocess(src);
          }
          let tokens = lexer2(src, opt);
          if (opt.hooks) {
            tokens = opt.hooks.processAllTokens(tokens);
          }
          if (opt.walkTokens) {
            this.walkTokens(tokens, opt.walkTokens);
          }
          let html2 = parser2(tokens, opt);
          if (opt.hooks) {
            html2 = opt.hooks.postprocess(html2);
          }
          return html2;
        } catch (e) {
          return throwError(e);
        }
      };
      return parse2;
    }
    onError(silent, async) {
      return (e) => {
        e.message += "\nPlease report this to https://github.com/markedjs/marked.";
        if (silent) {
          const msg = "<p>An error occurred:</p><pre>" + escape2(e.message + "", true) + "</pre>";
          if (async) {
            return Promise.resolve(msg);
          }
          return msg;
        }
        if (async) {
          return Promise.reject(e);
        }
        throw e;
      };
    }
  };

  // src/marked.ts
  var markedInstance = new Marked();
  function marked(src, opt) {
    return markedInstance.parse(src, opt);
  }
  marked.options = marked.setOptions = function(options2) {
    markedInstance.setOptions(options2);
    marked.defaults = markedInstance.defaults;
    changeDefaults(marked.defaults);
    return marked;
  };
  marked.getDefaults = _getDefaults;
  marked.defaults = _defaults;
  marked.use = function(...args) {
    markedInstance.use(...args);
    marked.defaults = markedInstance.defaults;
    changeDefaults(marked.defaults);
    return marked;
  };
  marked.walkTokens = function(tokens, callback) {
    return markedInstance.walkTokens(tokens, callback);
  };
  marked.parseInline = markedInstance.parseInline;
  marked.Parser = _Parser;
  marked.parser = _Parser.parse;
  marked.Renderer = _Renderer;
  marked.TextRenderer = _TextRenderer;
  marked.Lexer = _Lexer;
  marked.lexer = _Lexer.lex;
  marked.Tokenizer = _Tokenizer;
  marked.Hooks = _Hooks;
  marked.parse = marked;
  marked.options;
  marked.setOptions;
  marked.use;
  marked.walkTokens;
  marked.parseInline;
  _Parser.parse;
  _Lexer.lex;

  /*! @license DOMPurify 3.2.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.6/LICENSE */

  const {
    entries,
    setPrototypeOf,
    isFrozen,
    getPrototypeOf,
    getOwnPropertyDescriptor
  } = Object;
  let {
    freeze,
    seal,
    create
  } = Object; // eslint-disable-line import/no-mutable-exports
  let {
    apply,
    construct
  } = typeof Reflect !== 'undefined' && Reflect;
  if (!freeze) {
    freeze = function freeze(x) {
      return x;
    };
  }
  if (!seal) {
    seal = function seal(x) {
      return x;
    };
  }
  if (!apply) {
    apply = function apply(fun, thisValue, args) {
      return fun.apply(thisValue, args);
    };
  }
  if (!construct) {
    construct = function construct(Func, args) {
      return new Func(...args);
    };
  }
  const arrayForEach = unapply(Array.prototype.forEach);
  const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
  const arrayPop = unapply(Array.prototype.pop);
  const arrayPush = unapply(Array.prototype.push);
  const arraySplice = unapply(Array.prototype.splice);
  const stringToLowerCase = unapply(String.prototype.toLowerCase);
  const stringToString = unapply(String.prototype.toString);
  const stringMatch = unapply(String.prototype.match);
  const stringReplace = unapply(String.prototype.replace);
  const stringIndexOf = unapply(String.prototype.indexOf);
  const stringTrim = unapply(String.prototype.trim);
  const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
  const regExpTest = unapply(RegExp.prototype.test);
  const typeErrorCreate = unconstruct(TypeError);
  /**
   * Creates a new function that calls the given function with a specified thisArg and arguments.
   *
   * @param func - The function to be wrapped and called.
   * @returns A new function that calls the given function with a specified thisArg and arguments.
   */
  function unapply(func) {
    return function (thisArg) {
      if (thisArg instanceof RegExp) {
        thisArg.lastIndex = 0;
      }
      for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
        args[_key - 1] = arguments[_key];
      }
      return apply(func, thisArg, args);
    };
  }
  /**
   * Creates a new function that constructs an instance of the given constructor function with the provided arguments.
   *
   * @param func - The constructor function to be wrapped and called.
   * @returns A new function that constructs an instance of the given constructor function with the provided arguments.
   */
  function unconstruct(func) {
    return function () {
      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
        args[_key2] = arguments[_key2];
      }
      return construct(func, args);
    };
  }
  /**
   * Add properties to a lookup table
   *
   * @param set - The set to which elements will be added.
   * @param array - The array containing elements to be added to the set.
   * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
   * @returns The modified set with added elements.
   */
  function addToSet(set, array) {
    let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
    if (setPrototypeOf) {
      // Make 'in' and truthy checks like Boolean(set.constructor)
      // independent of any properties defined on Object.prototype.
      // Prevent prototype setters from intercepting set as a this value.
      setPrototypeOf(set, null);
    }
    let l = array.length;
    while (l--) {
      let element = array[l];
      if (typeof element === 'string') {
        const lcElement = transformCaseFunc(element);
        if (lcElement !== element) {
          // Config presets (e.g. tags.js, attrs.js) are immutable.
          if (!isFrozen(array)) {
            array[l] = lcElement;
          }
          element = lcElement;
        }
      }
      set[element] = true;
    }
    return set;
  }
  /**
   * Clean up an array to harden against CSPP
   *
   * @param array - The array to be cleaned.
   * @returns The cleaned version of the array
   */
  function cleanArray(array) {
    for (let index = 0; index < array.length; index++) {
      const isPropertyExist = objectHasOwnProperty(array, index);
      if (!isPropertyExist) {
        array[index] = null;
      }
    }
    return array;
  }
  /**
   * Shallow clone an object
   *
   * @param object - The object to be cloned.
   * @returns A new object that copies the original.
   */
  function clone(object) {
    const newObject = create(null);
    for (const [property, value] of entries(object)) {
      const isPropertyExist = objectHasOwnProperty(object, property);
      if (isPropertyExist) {
        if (Array.isArray(value)) {
          newObject[property] = cleanArray(value);
        } else if (value && typeof value === 'object' && value.constructor === Object) {
          newObject[property] = clone(value);
        } else {
          newObject[property] = value;
        }
      }
    }
    return newObject;
  }
  /**
   * This method automatically checks if the prop is function or getter and behaves accordingly.
   *
   * @param object - The object to look up the getter function in its prototype chain.
   * @param prop - The property name for which to find the getter function.
   * @returns The getter function found in the prototype chain or a fallback function.
   */
  function lookupGetter(object, prop) {
    while (object !== null) {
      const desc = getOwnPropertyDescriptor(object, prop);
      if (desc) {
        if (desc.get) {
          return unapply(desc.get);
        }
        if (typeof desc.value === 'function') {
          return unapply(desc.value);
        }
      }
      object = getPrototypeOf(object);
    }
    function fallbackValue() {
      return null;
    }
    return fallbackValue;
  }

  const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
  const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
  const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
  // List of SVG elements that are disallowed by default.
  // We still need to know them so that we can do namespace
  // checks properly in case one wants to add them to
  // allow-list.
  const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
  const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);
  // Similarly to SVG, we want to know all MathML elements,
  // even those that we disallow by default.
  const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
  const text = freeze(['#text']);

  const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
  const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
  const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
  const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);

  // eslint-disable-next-line unicorn/better-regex
  const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
  const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
  const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
  const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
  const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
  const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
  );
  const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
  const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
  );
  const DOCTYPE_NAME = seal(/^html$/i);
  const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);

  var EXPRESSIONS = /*#__PURE__*/Object.freeze({
    __proto__: null,
    ARIA_ATTR: ARIA_ATTR,
    ATTR_WHITESPACE: ATTR_WHITESPACE,
    CUSTOM_ELEMENT: CUSTOM_ELEMENT,
    DATA_ATTR: DATA_ATTR,
    DOCTYPE_NAME: DOCTYPE_NAME,
    ERB_EXPR: ERB_EXPR,
    IS_ALLOWED_URI: IS_ALLOWED_URI,
    IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
    MUSTACHE_EXPR: MUSTACHE_EXPR,
    TMPLIT_EXPR: TMPLIT_EXPR
  });

  /* eslint-disable @typescript-eslint/indent */
  // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
  const NODE_TYPE = {
    element: 1,
    text: 3,
    // Deprecated
    progressingInstruction: 7,
    comment: 8,
    document: 9};
  const getGlobal = function getGlobal() {
    return typeof window === 'undefined' ? null : window;
  };
  /**
   * Creates a no-op policy for internal use only.
   * Don't export this function outside this module!
   * @param trustedTypes The policy factory.
   * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
   * @return The policy created (or null, if Trusted Types
   * are not supported or creating the policy failed).
   */
  const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
    if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
      return null;
    }
    // Allow the callers to control the unique policy name
    // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
    // Policy creation with duplicate names throws in Trusted Types.
    let suffix = null;
    const ATTR_NAME = 'data-tt-policy-suffix';
    if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
      suffix = purifyHostElement.getAttribute(ATTR_NAME);
    }
    const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
    try {
      return trustedTypes.createPolicy(policyName, {
        createHTML(html) {
          return html;
        },
        createScriptURL(scriptUrl) {
          return scriptUrl;
        }
      });
    } catch (_) {
      // Policy creation failed (most likely another DOMPurify script has
      // already run). Skip creating the policy, as this will only cause errors
      // if TT are enforced.
      console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
      return null;
    }
  };
  const _createHooksMap = function _createHooksMap() {
    return {
      afterSanitizeAttributes: [],
      afterSanitizeElements: [],
      afterSanitizeShadowDOM: [],
      beforeSanitizeAttributes: [],
      beforeSanitizeElements: [],
      beforeSanitizeShadowDOM: [],
      uponSanitizeAttribute: [],
      uponSanitizeElement: [],
      uponSanitizeShadowNode: []
    };
  };
  function createDOMPurify() {
    let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
    const DOMPurify = root => createDOMPurify(root);
    DOMPurify.version = '3.2.6';
    DOMPurify.removed = [];
    if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
      // Not running in a browser, provide a factory function
      // so that you can pass your own Window
      DOMPurify.isSupported = false;
      return DOMPurify;
    }
    let {
      document
    } = window;
    const originalDocument = document;
    const currentScript = originalDocument.currentScript;
    const {
      DocumentFragment,
      HTMLTemplateElement,
      Node,
      Element,
      NodeFilter,
      NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
      HTMLFormElement,
      DOMParser,
      trustedTypes
    } = window;
    const ElementPrototype = Element.prototype;
    const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
    const remove = lookupGetter(ElementPrototype, 'remove');
    const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
    const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
    const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
    // As per issue #47, the web-components registry is inherited by a
    // new document created via createHTMLDocument. As per the spec
    // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
    // a new empty registry is used when creating a template contents owner
    // document, so we use that as our parent document to ensure nothing
    // is inherited.
    if (typeof HTMLTemplateElement === 'function') {
      const template = document.createElement('template');
      if (template.content && template.content.ownerDocument) {
        document = template.content.ownerDocument;
      }
    }
    let trustedTypesPolicy;
    let emptyHTML = '';
    const {
      implementation,
      createNodeIterator,
      createDocumentFragment,
      getElementsByTagName
    } = document;
    const {
      importNode
    } = originalDocument;
    let hooks = _createHooksMap();
    /**
     * Expose whether this browser supports running the full DOMPurify.
     */
    DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
    const {
      MUSTACHE_EXPR,
      ERB_EXPR,
      TMPLIT_EXPR,
      DATA_ATTR,
      ARIA_ATTR,
      IS_SCRIPT_OR_DATA,
      ATTR_WHITESPACE,
      CUSTOM_ELEMENT
    } = EXPRESSIONS;
    let {
      IS_ALLOWED_URI: IS_ALLOWED_URI$1
    } = EXPRESSIONS;
    /**
     * We consider the elements and attributes below to be safe. Ideally
     * don't add any new ones but feel free to remove unwanted ones.
     */
    /* allowed element names */
    let ALLOWED_TAGS = null;
    const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
    /* Allowed attribute names */
    let ALLOWED_ATTR = null;
    const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
    /*
     * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.
     * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
     * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
     * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
     */
    let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, {
      tagNameCheck: {
        writable: true,
        configurable: false,
        enumerable: true,
        value: null
      },
      attributeNameCheck: {
        writable: true,
        configurable: false,
        enumerable: true,
        value: null
      },
      allowCustomizedBuiltInElements: {
        writable: true,
        configurable: false,
        enumerable: true,
        value: false
      }
    }));
    /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
    let FORBID_TAGS = null;
    /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
    let FORBID_ATTR = null;
    /* Decide if ARIA attributes are okay */
    let ALLOW_ARIA_ATTR = true;
    /* Decide if custom data attributes are okay */
    let ALLOW_DATA_ATTR = true;
    /* Decide if unknown protocols are okay */
    let ALLOW_UNKNOWN_PROTOCOLS = false;
    /* Decide if self-closing tags in attributes are allowed.
     * Usually removed due to a mXSS issue in jQuery 3.0 */
    let ALLOW_SELF_CLOSE_IN_ATTR = true;
    /* Output should be safe for common template engines.
     * This means, DOMPurify removes data attributes, mustaches and ERB
     */
    let SAFE_FOR_TEMPLATES = false;
    /* Output should be safe even for XML used within HTML and alike.
     * This means, DOMPurify removes comments when containing risky content.
     */
    let SAFE_FOR_XML = true;
    /* Decide if document with <html>... should be returned */
    let WHOLE_DOCUMENT = false;
    /* Track whether config is already set on this instance of DOMPurify. */
    let SET_CONFIG = false;
    /* Decide if all elements (e.g. style, script) must be children of
     * document.body. By default, browsers might move them to document.head */
    let FORCE_BODY = false;
    /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
     * string (or a TrustedHTML object if Trusted Types are supported).
     * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
     */
    let RETURN_DOM = false;
    /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
     * string  (or a TrustedHTML object if Trusted Types are supported) */
    let RETURN_DOM_FRAGMENT = false;
    /* Try to return a Trusted Type object instead of a string, return a string in
     * case Trusted Types are not supported  */
    let RETURN_TRUSTED_TYPE = false;
    /* Output should be free from DOM clobbering attacks?
     * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
     */
    let SANITIZE_DOM = true;
    /* Achieve full DOM Clobbering protection by isolating the namespace of named
     * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
     *
     * HTML/DOM spec rules that enable DOM Clobbering:
     *   - Named Access on Window (§7.3.3)
     *   - DOM Tree Accessors (§3.1.5)
     *   - Form Element Parent-Child Relations (§4.10.3)
     *   - Iframe srcdoc / Nested WindowProxies (§4.8.5)
     *   - HTMLCollection (§4.2.10.2)
     *
     * Namespace isolation is implemented by prefixing `id` and `name` attributes
     * with a constant string, i.e., `user-content-`
     */
    let SANITIZE_NAMED_PROPS = false;
    const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
    /* Keep element content when removing element? */
    let KEEP_CONTENT = true;
    /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
     * of importing it into a new Document and returning a sanitized copy */
    let IN_PLACE = false;
    /* Allow usage of profiles like html, svg and mathMl */
    let USE_PROFILES = {};
    /* Tags to ignore content of when KEEP_CONTENT is true */
    let FORBID_CONTENTS = null;
    const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
    /* Tags that are safe for data: URIs */
    let DATA_URI_TAGS = null;
    const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
    /* Attributes safe for values like "javascript:" */
    let URI_SAFE_ATTRIBUTES = null;
    const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
    const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
    const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
    const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
    /* Document namespace */
    let NAMESPACE = HTML_NAMESPACE;
    let IS_EMPTY_INPUT = false;
    /* Allowed XHTML+XML namespaces */
    let ALLOWED_NAMESPACES = null;
    const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
    let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
    let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
    // Certain elements are allowed in both SVG and HTML
    // namespace. We need to specify them explicitly
    // so that they don't get erroneously deleted from
    // HTML namespace.
    const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
    /* Parsing of strict XHTML documents */
    let PARSER_MEDIA_TYPE = null;
    const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
    const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
    let transformCaseFunc = null;
    /* Keep a reference to config to pass to hooks */
    let CONFIG = null;
    /* Ideally, do not touch anything below this line */
    /* ______________________________________________ */
    const formElement = document.createElement('form');
    const isRegexOrFunction = function isRegexOrFunction(testValue) {
      return testValue instanceof RegExp || testValue instanceof Function;
    };
    /**
     * _parseConfig
     *
     * @param cfg optional config literal
     */
    // eslint-disable-next-line complexity
    const _parseConfig = function _parseConfig() {
      let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      if (CONFIG && CONFIG === cfg) {
        return;
      }
      /* Shield configuration object from tampering */
      if (!cfg || typeof cfg !== 'object') {
        cfg = {};
      }
      /* Shield configuration object from prototype pollution */
      cfg = clone(cfg);
      PARSER_MEDIA_TYPE =
      // eslint-disable-next-line unicorn/prefer-includes
      SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
      // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
      transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
      /* Set configuration parameters */
      ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
      ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
      ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
      URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
      DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
      FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
      FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
      FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
      USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
      ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
      ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
      ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
      ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
      SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
      SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
      WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
      RETURN_DOM = cfg.RETURN_DOM || false; // Default false
      RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
      RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
      FORCE_BODY = cfg.FORCE_BODY || false; // Default false
      SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
      SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
      KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
      IN_PLACE = cfg.IN_PLACE || false; // Default false
      IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
      NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
      MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
      HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
      CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
        CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
      }
      if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
        CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
      }
      if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
        CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
      }
      if (SAFE_FOR_TEMPLATES) {
        ALLOW_DATA_ATTR = false;
      }
      if (RETURN_DOM_FRAGMENT) {
        RETURN_DOM = true;
      }
      /* Parse profile info */
      if (USE_PROFILES) {
        ALLOWED_TAGS = addToSet({}, text);
        ALLOWED_ATTR = [];
        if (USE_PROFILES.html === true) {
          addToSet(ALLOWED_TAGS, html$1);
          addToSet(ALLOWED_ATTR, html);
        }
        if (USE_PROFILES.svg === true) {
          addToSet(ALLOWED_TAGS, svg$1);
          addToSet(ALLOWED_ATTR, svg);
          addToSet(ALLOWED_ATTR, xml);
        }
        if (USE_PROFILES.svgFilters === true) {
          addToSet(ALLOWED_TAGS, svgFilters);
          addToSet(ALLOWED_ATTR, svg);
          addToSet(ALLOWED_ATTR, xml);
        }
        if (USE_PROFILES.mathMl === true) {
          addToSet(ALLOWED_TAGS, mathMl$1);
          addToSet(ALLOWED_ATTR, mathMl);
          addToSet(ALLOWED_ATTR, xml);
        }
      }
      /* Merge configuration parameters */
      if (cfg.ADD_TAGS) {
        if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
          ALLOWED_TAGS = clone(ALLOWED_TAGS);
        }
        addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
      }
      if (cfg.ADD_ATTR) {
        if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
          ALLOWED_ATTR = clone(ALLOWED_ATTR);
        }
        addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
      }
      if (cfg.ADD_URI_SAFE_ATTR) {
        addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
      }
      if (cfg.FORBID_CONTENTS) {
        if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
          FORBID_CONTENTS = clone(FORBID_CONTENTS);
        }
        addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
      }
      /* Add #text in case KEEP_CONTENT is set to true */
      if (KEEP_CONTENT) {
        ALLOWED_TAGS['#text'] = true;
      }
      /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
      if (WHOLE_DOCUMENT) {
        addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
      }
      /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
      if (ALLOWED_TAGS.table) {
        addToSet(ALLOWED_TAGS, ['tbody']);
        delete FORBID_TAGS.tbody;
      }
      if (cfg.TRUSTED_TYPES_POLICY) {
        if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
        }
        if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
          throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
        }
        // Overwrite existing TrustedTypes policy.
        trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
        // Sign local variables required by `sanitize`.
        emptyHTML = trustedTypesPolicy.createHTML('');
      } else {
        // Uninitialized policy, attempt to initialize the internal dompurify policy.
        if (trustedTypesPolicy === undefined) {
          trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
        }
        // If creating the internal policy succeeded sign internal variables.
        if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
          emptyHTML = trustedTypesPolicy.createHTML('');
        }
      }
      // Prevent further manipulation of configuration.
      // Not available in IE8, Safari 5, etc.
      if (freeze) {
        freeze(cfg);
      }
      CONFIG = cfg;
    };
    /* Keep track of all possible SVG and MathML tags
     * so that we can perform the namespace checks
     * correctly. */
    const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
    const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
    /**
     * @param element a DOM element whose namespace is being checked
     * @returns Return false if the element has a
     *  namespace that a spec-compliant parser would never
     *  return. Return true otherwise.
     */
    const _checkValidNamespace = function _checkValidNamespace(element) {
      let parent = getParentNode(element);
      // In JSDOM, if we're inside shadow DOM, then parentNode
      // can be null. We just simulate parent in this case.
      if (!parent || !parent.tagName) {
        parent = {
          namespaceURI: NAMESPACE,
          tagName: 'template'
        };
      }
      const tagName = stringToLowerCase(element.tagName);
      const parentTagName = stringToLowerCase(parent.tagName);
      if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
        return false;
      }
      if (element.namespaceURI === SVG_NAMESPACE) {
        // The only way to switch from HTML namespace to SVG
        // is via <svg>. If it happens via any other tag, then
        // it should be killed.
        if (parent.namespaceURI === HTML_NAMESPACE) {
          return tagName === 'svg';
        }
        // The only way to switch from MathML to SVG is via`
        // svg if parent is either <annotation-xml> or MathML
        // text integration points.
        if (parent.namespaceURI === MATHML_NAMESPACE) {
          return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
        }
        // We only allow elements that are defined in SVG
        // spec. All others are disallowed in SVG namespace.
        return Boolean(ALL_SVG_TAGS[tagName]);
      }
      if (element.namespaceURI === MATHML_NAMESPACE) {
        // The only way to switch from HTML namespace to MathML
        // is via <math>. If it happens via any other tag, then
        // it should be killed.
        if (parent.namespaceURI === HTML_NAMESPACE) {
          return tagName === 'math';
        }
        // The only way to switch from SVG to MathML is via
        // <math> and HTML integration points
        if (parent.namespaceURI === SVG_NAMESPACE) {
          return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
        }
        // We only allow elements that are defined in MathML
        // spec. All others are disallowed in MathML namespace.
        return Boolean(ALL_MATHML_TAGS[tagName]);
      }
      if (element.namespaceURI === HTML_NAMESPACE) {
        // The only way to switch from SVG to HTML is via
        // HTML integration points, and from MathML to HTML
        // is via MathML text integration points
        if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
          return false;
        }
        if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
          return false;
        }
        // We disallow tags that are specific for MathML
        // or SVG and should never appear in HTML namespace
        return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
      }
      // For XHTML and XML documents that support custom namespaces
      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
        return true;
      }
      // The code should never reach this place (this means
      // that the element somehow got namespace that is not
      // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
      // Return false just in case.
      return false;
    };
    /**
     * _forceRemove
     *
     * @param node a DOM node
     */
    const _forceRemove = function _forceRemove(node) {
      arrayPush(DOMPurify.removed, {
        element: node
      });
      try {
        // eslint-disable-next-line unicorn/prefer-dom-node-remove
        getParentNode(node).removeChild(node);
      } catch (_) {
        remove(node);
      }
    };
    /**
     * _removeAttribute
     *
     * @param name an Attribute name
     * @param element a DOM node
     */
    const _removeAttribute = function _removeAttribute(name, element) {
      try {
        arrayPush(DOMPurify.removed, {
          attribute: element.getAttributeNode(name),
          from: element
        });
      } catch (_) {
        arrayPush(DOMPurify.removed, {
          attribute: null,
          from: element
        });
      }
      element.removeAttribute(name);
      // We void attribute values for unremovable "is" attributes
      if (name === 'is') {
        if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
          try {
            _forceRemove(element);
          } catch (_) {}
        } else {
          try {
            element.setAttribute(name, '');
          } catch (_) {}
        }
      }
    };
    /**
     * _initDocument
     *
     * @param dirty - a string of dirty markup
     * @return a DOM, filled with the dirty markup
     */
    const _initDocument = function _initDocument(dirty) {
      /* Create a HTML document */
      let doc = null;
      let leadingWhitespace = null;
      if (FORCE_BODY) {
        dirty = '<remove></remove>' + dirty;
      } else {
        /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
        const matches = stringMatch(dirty, /^[\r\n\t ]+/);
        leadingWhitespace = matches && matches[0];
      }
      if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
        // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
        dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
      }
      const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
      /*
       * Use the DOMParser API by default, fallback later if needs be
       * DOMParser not work for svg when has multiple root element.
       */
      if (NAMESPACE === HTML_NAMESPACE) {
        try {
          doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
        } catch (_) {}
      }
      /* Use createHTMLDocument in case DOMParser is not available */
      if (!doc || !doc.documentElement) {
        doc = implementation.createDocument(NAMESPACE, 'template', null);
        try {
          doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
        } catch (_) {
          // Syntax error if dirtyPayload is invalid xml
        }
      }
      const body = doc.body || doc.documentElement;
      if (dirty && leadingWhitespace) {
        body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
      }
      /* Work on whole document or just its body */
      if (NAMESPACE === HTML_NAMESPACE) {
        return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
      }
      return WHOLE_DOCUMENT ? doc.documentElement : body;
    };
    /**
     * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
     *
     * @param root The root element or node to start traversing on.
     * @return The created NodeIterator
     */
    const _createNodeIterator = function _createNodeIterator(root) {
      return createNodeIterator.call(root.ownerDocument || root, root,
      // eslint-disable-next-line no-bitwise
      NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
    };
    /**
     * _isClobbered
     *
     * @param element element to check for clobbering attacks
     * @return true if clobbered, false if safe
     */
    const _isClobbered = function _isClobbered(element) {
      return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');
    };
    /**
     * Checks whether the given object is a DOM node.
     *
     * @param value object to check whether it's a DOM node
     * @return true is object is a DOM node
     */
    const _isNode = function _isNode(value) {
      return typeof Node === 'function' && value instanceof Node;
    };
    function _executeHooks(hooks, currentNode, data) {
      arrayForEach(hooks, hook => {
        hook.call(DOMPurify, currentNode, data, CONFIG);
      });
    }
    /**
     * _sanitizeElements
     *
     * @protect nodeName
     * @protect textContent
     * @protect removeChild
     * @param currentNode to check for permission to exist
     * @return true if node was killed, false if left alive
     */
    const _sanitizeElements = function _sanitizeElements(currentNode) {
      let content = null;
      /* Execute a hook if present */
      _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
      /* Check if element is clobbered or can clobber */
      if (_isClobbered(currentNode)) {
        _forceRemove(currentNode);
        return true;
      }
      /* Now let's check the element's type and name */
      const tagName = transformCaseFunc(currentNode.nodeName);
      /* Execute a hook if present */
      _executeHooks(hooks.uponSanitizeElement, currentNode, {
        tagName,
        allowedTags: ALLOWED_TAGS
      });
      /* Detect mXSS attempts abusing namespace confusion */
      if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
        _forceRemove(currentNode);
        return true;
      }
      /* Remove any occurrence of processing instructions */
      if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
        _forceRemove(currentNode);
        return true;
      }
      /* Remove any kind of possibly harmful comments */
      if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
        _forceRemove(currentNode);
        return true;
      }
      /* Remove element if anything forbids its presence */
      if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
        /* Check if we have a custom element to handle */
        if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
            return false;
          }
          if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
            return false;
          }
        }
        /* Keep content except for bad-listed elements */
        if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
          const parentNode = getParentNode(currentNode) || currentNode.parentNode;
          const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
          if (childNodes && parentNode) {
            const childCount = childNodes.length;
            for (let i = childCount - 1; i >= 0; --i) {
              const childClone = cloneNode(childNodes[i], true);
              childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
              parentNode.insertBefore(childClone, getNextSibling(currentNode));
            }
          }
        }
        _forceRemove(currentNode);
        return true;
      }
      /* Check whether element has a valid namespace */
      if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
        _forceRemove(currentNode);
        return true;
      }
      /* Make sure that older browsers don't get fallback-tag mXSS */
      if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
        _forceRemove(currentNode);
        return true;
      }
      /* Sanitize element content to be template-safe */
      if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
        /* Get the element's text content */
        content = currentNode.textContent;
        arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
          content = stringReplace(content, expr, ' ');
        });
        if (currentNode.textContent !== content) {
          arrayPush(DOMPurify.removed, {
            element: currentNode.cloneNode()
          });
          currentNode.textContent = content;
        }
      }
      /* Execute a hook if present */
      _executeHooks(hooks.afterSanitizeElements, currentNode, null);
      return false;
    };
    /**
     * _isValidAttribute
     *
     * @param lcTag Lowercase tag name of containing element.
     * @param lcName Lowercase attribute name.
     * @param value Attribute value.
     * @return Returns true if `value` is valid, otherwise false.
     */
    // eslint-disable-next-line complexity
    const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
      /* Make sure attribute cannot clobber */
      if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
        return false;
      }
      /* Allow valid data-* attributes: At least one character after "-"
          (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
          XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
          We don't need to check the value; it's always URI safe. */
      if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
        if (
        // First condition does a very basic check if a) it's basically a valid custom element tagname AND
        // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
        // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
        _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) ||
        // Alternative, second condition checks if it's an `is`-attribute, AND
        // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
        lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {
          return false;
        }
        /* Check value is safe. First, is attr inert? If so, is safe */
      } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
        return false;
      } else ;
      return true;
    };
    /**
     * _isBasicCustomElement
     * checks if at least one dash is included in tagName, and it's not the first char
     * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
     *
     * @param tagName name of the tag of the node to sanitize
     * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
     */
    const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
      return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
    };
    /**
     * _sanitizeAttributes
     *
     * @protect attributes
     * @protect nodeName
     * @protect removeAttribute
     * @protect setAttribute
     *
     * @param currentNode to sanitize
     */
    const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
      /* Execute a hook if present */
      _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
      const {
        attributes
      } = currentNode;
      /* Check if we have attributes; if not we might have a text node */
      if (!attributes || _isClobbered(currentNode)) {
        return;
      }
      const hookEvent = {
        attrName: '',
        attrValue: '',
        keepAttr: true,
        allowedAttributes: ALLOWED_ATTR,
        forceKeepAttr: undefined
      };
      let l = attributes.length;
      /* Go backwards over all attributes; safely remove bad ones */
      while (l--) {
        const attr = attributes[l];
        const {
          name,
          namespaceURI,
          value: attrValue
        } = attr;
        const lcName = transformCaseFunc(name);
        const initValue = attrValue;
        let value = name === 'value' ? initValue : stringTrim(initValue);
        /* Execute a hook if present */
        hookEvent.attrName = lcName;
        hookEvent.attrValue = value;
        hookEvent.keepAttr = true;
        hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
        _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
        value = hookEvent.attrValue;
        /* Full DOM Clobbering protection via namespace isolation,
         * Prefix id and name attributes with `user-content-`
         */
        if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
          // Remove the attribute with this value
          _removeAttribute(name, currentNode);
          // Prefix the value and later re-create the attribute with the sanitized value
          value = SANITIZE_NAMED_PROPS_PREFIX + value;
        }
        /* Work around a security issue with comments inside attributes */
        if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
          _removeAttribute(name, currentNode);
          continue;
        }
        /* Did the hooks approve of the attribute? */
        if (hookEvent.forceKeepAttr) {
          continue;
        }
        /* Did the hooks approve of the attribute? */
        if (!hookEvent.keepAttr) {
          _removeAttribute(name, currentNode);
          continue;
        }
        /* Work around a security issue in jQuery 3.0 */
        if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
          _removeAttribute(name, currentNode);
          continue;
        }
        /* Sanitize attribute content to be template-safe */
        if (SAFE_FOR_TEMPLATES) {
          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
            value = stringReplace(value, expr, ' ');
          });
        }
        /* Is `value` valid for this attribute? */
        const lcTag = transformCaseFunc(currentNode.nodeName);
        if (!_isValidAttribute(lcTag, lcName, value)) {
          _removeAttribute(name, currentNode);
          continue;
        }
        /* Handle attributes that require Trusted Types */
        if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
          if (namespaceURI) ; else {
            switch (trustedTypes.getAttributeType(lcTag, lcName)) {
              case 'TrustedHTML':
                {
                  value = trustedTypesPolicy.createHTML(value);
                  break;
                }
              case 'TrustedScriptURL':
                {
                  value = trustedTypesPolicy.createScriptURL(value);
                  break;
                }
            }
          }
        }
        /* Handle invalid data-* attribute set by try-catching it */
        if (value !== initValue) {
          try {
            if (namespaceURI) {
              currentNode.setAttributeNS(namespaceURI, name, value);
            } else {
              /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
              currentNode.setAttribute(name, value);
            }
            if (_isClobbered(currentNode)) {
              _forceRemove(currentNode);
            } else {
              arrayPop(DOMPurify.removed);
            }
          } catch (_) {
            _removeAttribute(name, currentNode);
          }
        }
      }
      /* Execute a hook if present */
      _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
    };
    /**
     * _sanitizeShadowDOM
     *
     * @param fragment to iterate over recursively
     */
    const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
      let shadowNode = null;
      const shadowIterator = _createNodeIterator(fragment);
      /* Execute a hook if present */
      _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
      while (shadowNode = shadowIterator.nextNode()) {
        /* Execute a hook if present */
        _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
        /* Sanitize tags and elements */
        _sanitizeElements(shadowNode);
        /* Check attributes next */
        _sanitizeAttributes(shadowNode);
        /* Deep shadow DOM detected */
        if (shadowNode.content instanceof DocumentFragment) {
          _sanitizeShadowDOM(shadowNode.content);
        }
      }
      /* Execute a hook if present */
      _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
    };
    // eslint-disable-next-line complexity
    DOMPurify.sanitize = function (dirty) {
      let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      let body = null;
      let importedNode = null;
      let currentNode = null;
      let returnNode = null;
      /* Make sure we have a string to sanitize.
        DO NOT return early, as this will return the wrong type if
        the user has requested a DOM object rather than a string */
      IS_EMPTY_INPUT = !dirty;
      if (IS_EMPTY_INPUT) {
        dirty = '<!-->';
      }
      /* Stringify, in case dirty is an object */
      if (typeof dirty !== 'string' && !_isNode(dirty)) {
        if (typeof dirty.toString === 'function') {
          dirty = dirty.toString();
          if (typeof dirty !== 'string') {
            throw typeErrorCreate('dirty is not a string, aborting');
          }
        } else {
          throw typeErrorCreate('toString is not a function');
        }
      }
      /* Return dirty HTML if DOMPurify cannot run */
      if (!DOMPurify.isSupported) {
        return dirty;
      }
      /* Assign config vars */
      if (!SET_CONFIG) {
        _parseConfig(cfg);
      }
      /* Clean up removed elements */
      DOMPurify.removed = [];
      /* Check if dirty is correctly typed for IN_PLACE */
      if (typeof dirty === 'string') {
        IN_PLACE = false;
      }
      if (IN_PLACE) {
        /* Do some early pre-sanitization to avoid unsafe root nodes */
        if (dirty.nodeName) {
          const tagName = transformCaseFunc(dirty.nodeName);
          if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
            throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
          }
        }
      } else if (dirty instanceof Node) {
        /* If dirty is a DOM element, append to an empty document to avoid
           elements being stripped by the parser */
        body = _initDocument('<!---->');
        importedNode = body.ownerDocument.importNode(dirty, true);
        if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
          /* Node is already a body, use as is */
          body = importedNode;
        } else if (importedNode.nodeName === 'HTML') {
          body = importedNode;
        } else {
          // eslint-disable-next-line unicorn/prefer-dom-node-append
          body.appendChild(importedNode);
        }
      } else {
        /* Exit directly if we have nothing to do */
        if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
        // eslint-disable-next-line unicorn/prefer-includes
        dirty.indexOf('<') === -1) {
          return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
        }
        /* Initialize the document to work on */
        body = _initDocument(dirty);
        /* Check we have a DOM node from the data */
        if (!body) {
          return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
        }
      }
      /* Remove first element node (ours) if FORCE_BODY is set */
      if (body && FORCE_BODY) {
        _forceRemove(body.firstChild);
      }
      /* Get node iterator */
      const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
      /* Now start iterating over the created document */
      while (currentNode = nodeIterator.nextNode()) {
        /* Sanitize tags and elements */
        _sanitizeElements(currentNode);
        /* Check attributes next */
        _sanitizeAttributes(currentNode);
        /* Shadow DOM detected, sanitize it */
        if (currentNode.content instanceof DocumentFragment) {
          _sanitizeShadowDOM(currentNode.content);
        }
      }
      /* If we sanitized `dirty` in-place, return it. */
      if (IN_PLACE) {
        return dirty;
      }
      /* Return sanitized string or DOM */
      if (RETURN_DOM) {
        if (RETURN_DOM_FRAGMENT) {
          returnNode = createDocumentFragment.call(body.ownerDocument);
          while (body.firstChild) {
            // eslint-disable-next-line unicorn/prefer-dom-node-append
            returnNode.appendChild(body.firstChild);
          }
        } else {
          returnNode = body;
        }
        if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
          /*
            AdoptNode() is not used because internal state is not reset
            (e.g. the past names map of a HTMLFormElement), this is safe
            in theory but we would rather not risk another attack vector.
            The state that is cloned by importNode() is explicitly defined
            by the specs.
          */
          returnNode = importNode.call(originalDocument, returnNode, true);
        }
        return returnNode;
      }
      let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
      /* Serialize doctype if allowed */
      if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
        serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
      }
      /* Sanitize final string template-safe */
      if (SAFE_FOR_TEMPLATES) {
        arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
          serializedHTML = stringReplace(serializedHTML, expr, ' ');
        });
      }
      return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
    };
    DOMPurify.setConfig = function () {
      let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      _parseConfig(cfg);
      SET_CONFIG = true;
    };
    DOMPurify.clearConfig = function () {
      CONFIG = null;
      SET_CONFIG = false;
    };
    DOMPurify.isValidAttribute = function (tag, attr, value) {
      /* Initialize shared config vars if necessary. */
      if (!CONFIG) {
        _parseConfig({});
      }
      const lcTag = transformCaseFunc(tag);
      const lcName = transformCaseFunc(attr);
      return _isValidAttribute(lcTag, lcName, value);
    };
    DOMPurify.addHook = function (entryPoint, hookFunction) {
      if (typeof hookFunction !== 'function') {
        return;
      }
      arrayPush(hooks[entryPoint], hookFunction);
    };
    DOMPurify.removeHook = function (entryPoint, hookFunction) {
      if (hookFunction !== undefined) {
        const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
        return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
      }
      return arrayPop(hooks[entryPoint]);
    };
    DOMPurify.removeHooks = function (entryPoint) {
      hooks[entryPoint] = [];
    };
    DOMPurify.removeAllHooks = function () {
      hooks = _createHooksMap();
    };
    return DOMPurify;
  }
  var purify = createDOMPurify();

  function getDefaultExportFromCjs (x) {
  	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
  }

  /* eslint-disable no-multi-assign */

  var core;
  var hasRequiredCore;

  function requireCore () {
  	if (hasRequiredCore) return core;
  	hasRequiredCore = 1;
  	function deepFreeze(obj) {
  	  if (obj instanceof Map) {
  	    obj.clear =
  	      obj.delete =
  	      obj.set =
  	        function () {
  	          throw new Error('map is read-only');
  	        };
  	  } else if (obj instanceof Set) {
  	    obj.add =
  	      obj.clear =
  	      obj.delete =
  	        function () {
  	          throw new Error('set is read-only');
  	        };
  	  }

  	  // Freeze self
  	  Object.freeze(obj);

  	  Object.getOwnPropertyNames(obj).forEach((name) => {
  	    const prop = obj[name];
  	    const type = typeof prop;

  	    // Freeze prop if it is an object or function and also not already frozen
  	    if ((type === 'object' || type === 'function') && !Object.isFrozen(prop)) {
  	      deepFreeze(prop);
  	    }
  	  });

  	  return obj;
  	}

  	/** @typedef {import('highlight.js').CallbackResponse} CallbackResponse */
  	/** @typedef {import('highlight.js').CompiledMode} CompiledMode */
  	/** @implements CallbackResponse */

  	class Response {
  	  /**
  	   * @param {CompiledMode} mode
  	   */
  	  constructor(mode) {
  	    // eslint-disable-next-line no-undefined
  	    if (mode.data === undefined) mode.data = {};

  	    this.data = mode.data;
  	    this.isMatchIgnored = false;
  	  }

  	  ignoreMatch() {
  	    this.isMatchIgnored = true;
  	  }
  	}

  	/**
  	 * @param {string} value
  	 * @returns {string}
  	 */
  	function escapeHTML(value) {
  	  return value
  	    .replace(/&/g, '&amp;')
  	    .replace(/</g, '&lt;')
  	    .replace(/>/g, '&gt;')
  	    .replace(/"/g, '&quot;')
  	    .replace(/'/g, '&#x27;');
  	}

  	/**
  	 * performs a shallow merge of multiple objects into one
  	 *
  	 * @template T
  	 * @param {T} original
  	 * @param {Record<string,any>[]} objects
  	 * @returns {T} a single new object
  	 */
  	function inherit$1(original, ...objects) {
  	  /** @type Record<string,any> */
  	  const result = Object.create(null);

  	  for (const key in original) {
  	    result[key] = original[key];
  	  }
  	  objects.forEach(function(obj) {
  	    for (const key in obj) {
  	      result[key] = obj[key];
  	    }
  	  });
  	  return /** @type {T} */ (result);
  	}

  	/**
  	 * @typedef {object} Renderer
  	 * @property {(text: string) => void} addText
  	 * @property {(node: Node) => void} openNode
  	 * @property {(node: Node) => void} closeNode
  	 * @property {() => string} value
  	 */

  	/** @typedef {{scope?: string, language?: string, sublanguage?: boolean}} Node */
  	/** @typedef {{walk: (r: Renderer) => void}} Tree */
  	/** */

  	const SPAN_CLOSE = '</span>';

  	/**
  	 * Determines if a node needs to be wrapped in <span>
  	 *
  	 * @param {Node} node */
  	const emitsWrappingTags = (node) => {
  	  // rarely we can have a sublanguage where language is undefined
  	  // TODO: track down why
  	  return !!node.scope;
  	};

  	/**
  	 *
  	 * @param {string} name
  	 * @param {{prefix:string}} options
  	 */
  	const scopeToCSSClass = (name, { prefix }) => {
  	  // sub-language
  	  if (name.startsWith("language:")) {
  	    return name.replace("language:", "language-");
  	  }
  	  // tiered scope: comment.line
  	  if (name.includes(".")) {
  	    const pieces = name.split(".");
  	    return [
  	      `${prefix}${pieces.shift()}`,
  	      ...(pieces.map((x, i) => `${x}${"_".repeat(i + 1)}`))
  	    ].join(" ");
  	  }
  	  // simple scope
  	  return `${prefix}${name}`;
  	};

  	/** @type {Renderer} */
  	class HTMLRenderer {
  	  /**
  	   * Creates a new HTMLRenderer
  	   *
  	   * @param {Tree} parseTree - the parse tree (must support `walk` API)
  	   * @param {{classPrefix: string}} options
  	   */
  	  constructor(parseTree, options) {
  	    this.buffer = "";
  	    this.classPrefix = options.classPrefix;
  	    parseTree.walk(this);
  	  }

  	  /**
  	   * Adds texts to the output stream
  	   *
  	   * @param {string} text */
  	  addText(text) {
  	    this.buffer += escapeHTML(text);
  	  }

  	  /**
  	   * Adds a node open to the output stream (if needed)
  	   *
  	   * @param {Node} node */
  	  openNode(node) {
  	    if (!emitsWrappingTags(node)) return;

  	    const className = scopeToCSSClass(node.scope,
  	      { prefix: this.classPrefix });
  	    this.span(className);
  	  }

  	  /**
  	   * Adds a node close to the output stream (if needed)
  	   *
  	   * @param {Node} node */
  	  closeNode(node) {
  	    if (!emitsWrappingTags(node)) return;

  	    this.buffer += SPAN_CLOSE;
  	  }

  	  /**
  	   * returns the accumulated buffer
  	  */
  	  value() {
  	    return this.buffer;
  	  }

  	  // helpers

  	  /**
  	   * Builds a span element
  	   *
  	   * @param {string} className */
  	  span(className) {
  	    this.buffer += `<span class="${className}">`;
  	  }
  	}

  	/** @typedef {{scope?: string, language?: string, children: Node[]} | string} Node */
  	/** @typedef {{scope?: string, language?: string, children: Node[]} } DataNode */
  	/** @typedef {import('highlight.js').Emitter} Emitter */
  	/**  */

  	/** @returns {DataNode} */
  	const newNode = (opts = {}) => {
  	  /** @type DataNode */
  	  const result = { children: [] };
  	  Object.assign(result, opts);
  	  return result;
  	};

  	class TokenTree {
  	  constructor() {
  	    /** @type DataNode */
  	    this.rootNode = newNode();
  	    this.stack = [this.rootNode];
  	  }

  	  get top() {
  	    return this.stack[this.stack.length - 1];
  	  }

  	  get root() { return this.rootNode; }

  	  /** @param {Node} node */
  	  add(node) {
  	    this.top.children.push(node);
  	  }

  	  /** @param {string} scope */
  	  openNode(scope) {
  	    /** @type Node */
  	    const node = newNode({ scope });
  	    this.add(node);
  	    this.stack.push(node);
  	  }

  	  closeNode() {
  	    if (this.stack.length > 1) {
  	      return this.stack.pop();
  	    }
  	    // eslint-disable-next-line no-undefined
  	    return undefined;
  	  }

  	  closeAllNodes() {
  	    while (this.closeNode());
  	  }

  	  toJSON() {
  	    return JSON.stringify(this.rootNode, null, 4);
  	  }

  	  /**
  	   * @typedef { import("./html_renderer").Renderer } Renderer
  	   * @param {Renderer} builder
  	   */
  	  walk(builder) {
  	    // this does not
  	    return this.constructor._walk(builder, this.rootNode);
  	    // this works
  	    // return TokenTree._walk(builder, this.rootNode);
  	  }

  	  /**
  	   * @param {Renderer} builder
  	   * @param {Node} node
  	   */
  	  static _walk(builder, node) {
  	    if (typeof node === "string") {
  	      builder.addText(node);
  	    } else if (node.children) {
  	      builder.openNode(node);
  	      node.children.forEach((child) => this._walk(builder, child));
  	      builder.closeNode(node);
  	    }
  	    return builder;
  	  }

  	  /**
  	   * @param {Node} node
  	   */
  	  static _collapse(node) {
  	    if (typeof node === "string") return;
  	    if (!node.children) return;

  	    if (node.children.every(el => typeof el === "string")) {
  	      // node.text = node.children.join("");
  	      // delete node.children;
  	      node.children = [node.children.join("")];
  	    } else {
  	      node.children.forEach((child) => {
  	        TokenTree._collapse(child);
  	      });
  	    }
  	  }
  	}

  	/**
  	  Currently this is all private API, but this is the minimal API necessary
  	  that an Emitter must implement to fully support the parser.

  	  Minimal interface:

  	  - addText(text)
  	  - __addSublanguage(emitter, subLanguageName)
  	  - startScope(scope)
  	  - endScope()
  	  - finalize()
  	  - toHTML()

  	*/

  	/**
  	 * @implements {Emitter}
  	 */
  	class TokenTreeEmitter extends TokenTree {
  	  /**
  	   * @param {*} options
  	   */
  	  constructor(options) {
  	    super();
  	    this.options = options;
  	  }

  	  /**
  	   * @param {string} text
  	   */
  	  addText(text) {
  	    if (text === "") { return; }

  	    this.add(text);
  	  }

  	  /** @param {string} scope */
  	  startScope(scope) {
  	    this.openNode(scope);
  	  }

  	  endScope() {
  	    this.closeNode();
  	  }

  	  /**
  	   * @param {Emitter & {root: DataNode}} emitter
  	   * @param {string} name
  	   */
  	  __addSublanguage(emitter, name) {
  	    /** @type DataNode */
  	    const node = emitter.root;
  	    if (name) node.scope = `language:${name}`;

  	    this.add(node);
  	  }

  	  toHTML() {
  	    const renderer = new HTMLRenderer(this, this.options);
  	    return renderer.value();
  	  }

  	  finalize() {
  	    this.closeAllNodes();
  	    return true;
  	  }
  	}

  	/**
  	 * @param {string} value
  	 * @returns {RegExp}
  	 * */

  	/**
  	 * @param {RegExp | string } re
  	 * @returns {string}
  	 */
  	function source(re) {
  	  if (!re) return null;
  	  if (typeof re === "string") return re;

  	  return re.source;
  	}

  	/**
  	 * @param {RegExp | string } re
  	 * @returns {string}
  	 */
  	function lookahead(re) {
  	  return concat('(?=', re, ')');
  	}

  	/**
  	 * @param {RegExp | string } re
  	 * @returns {string}
  	 */
  	function anyNumberOfTimes(re) {
  	  return concat('(?:', re, ')*');
  	}

  	/**
  	 * @param {RegExp | string } re
  	 * @returns {string}
  	 */
  	function optional(re) {
  	  return concat('(?:', re, ')?');
  	}

  	/**
  	 * @param {...(RegExp | string) } args
  	 * @returns {string}
  	 */
  	function concat(...args) {
  	  const joined = args.map((x) => source(x)).join("");
  	  return joined;
  	}

  	/**
  	 * @param { Array<string | RegExp | Object> } args
  	 * @returns {object}
  	 */
  	function stripOptionsFromArgs(args) {
  	  const opts = args[args.length - 1];

  	  if (typeof opts === 'object' && opts.constructor === Object) {
  	    args.splice(args.length - 1, 1);
  	    return opts;
  	  } else {
  	    return {};
  	  }
  	}

  	/** @typedef { {capture?: boolean} } RegexEitherOptions */

  	/**
  	 * Any of the passed expresssions may match
  	 *
  	 * Creates a huge this | this | that | that match
  	 * @param {(RegExp | string)[] | [...(RegExp | string)[], RegexEitherOptions]} args
  	 * @returns {string}
  	 */
  	function either(...args) {
  	  /** @type { object & {capture?: boolean} }  */
  	  const opts = stripOptionsFromArgs(args);
  	  const joined = '('
  	    + (opts.capture ? "" : "?:")
  	    + args.map((x) => source(x)).join("|") + ")";
  	  return joined;
  	}

  	/**
  	 * @param {RegExp | string} re
  	 * @returns {number}
  	 */
  	function countMatchGroups(re) {
  	  return (new RegExp(re.toString() + '|')).exec('').length - 1;
  	}

  	/**
  	 * Does lexeme start with a regular expression match at the beginning
  	 * @param {RegExp} re
  	 * @param {string} lexeme
  	 */
  	function startsWith(re, lexeme) {
  	  const match = re && re.exec(lexeme);
  	  return match && match.index === 0;
  	}

  	// BACKREF_RE matches an open parenthesis or backreference. To avoid
  	// an incorrect parse, it additionally matches the following:
  	// - [...] elements, where the meaning of parentheses and escapes change
  	// - other escape sequences, so we do not misparse escape sequences as
  	//   interesting elements
  	// - non-matching or lookahead parentheses, which do not capture. These
  	//   follow the '(' with a '?'.
  	const BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;

  	// **INTERNAL** Not intended for outside usage
  	// join logically computes regexps.join(separator), but fixes the
  	// backreferences so they continue to match.
  	// it also places each individual regular expression into it's own
  	// match group, keeping track of the sequencing of those match groups
  	// is currently an exercise for the caller. :-)
  	/**
  	 * @param {(string | RegExp)[]} regexps
  	 * @param {{joinWith: string}} opts
  	 * @returns {string}
  	 */
  	function _rewriteBackreferences(regexps, { joinWith }) {
  	  let numCaptures = 0;

  	  return regexps.map((regex) => {
  	    numCaptures += 1;
  	    const offset = numCaptures;
  	    let re = source(regex);
  	    let out = '';

  	    while (re.length > 0) {
  	      const match = BACKREF_RE.exec(re);
  	      if (!match) {
  	        out += re;
  	        break;
  	      }
  	      out += re.substring(0, match.index);
  	      re = re.substring(match.index + match[0].length);
  	      if (match[0][0] === '\\' && match[1]) {
  	        // Adjust the backreference.
  	        out += '\\' + String(Number(match[1]) + offset);
  	      } else {
  	        out += match[0];
  	        if (match[0] === '(') {
  	          numCaptures++;
  	        }
  	      }
  	    }
  	    return out;
  	  }).map(re => `(${re})`).join(joinWith);
  	}

  	/** @typedef {import('highlight.js').Mode} Mode */
  	/** @typedef {import('highlight.js').ModeCallback} ModeCallback */

  	// Common regexps
  	const MATCH_NOTHING_RE = /\b\B/;
  	const IDENT_RE = '[a-zA-Z]\\w*';
  	const UNDERSCORE_IDENT_RE = '[a-zA-Z_]\\w*';
  	const NUMBER_RE = '\\b\\d+(\\.\\d+)?';
  	const C_NUMBER_RE = '(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)'; // 0x..., 0..., decimal, float
  	const BINARY_NUMBER_RE = '\\b(0b[01]+)'; // 0b...
  	const RE_STARTERS_RE = '!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~';

  	/**
  	* @param { Partial<Mode> & {binary?: string | RegExp} } opts
  	*/
  	const SHEBANG = (opts = {}) => {
  	  const beginShebang = /^#![ ]*\//;
  	  if (opts.binary) {
  	    opts.begin = concat(
  	      beginShebang,
  	      /.*\b/,
  	      opts.binary,
  	      /\b.*/);
  	  }
  	  return inherit$1({
  	    scope: 'meta',
  	    begin: beginShebang,
  	    end: /$/,
  	    relevance: 0,
  	    /** @type {ModeCallback} */
  	    "on:begin": (m, resp) => {
  	      if (m.index !== 0) resp.ignoreMatch();
  	    }
  	  }, opts);
  	};

  	// Common modes
  	const BACKSLASH_ESCAPE = {
  	  begin: '\\\\[\\s\\S]', relevance: 0
  	};
  	const APOS_STRING_MODE = {
  	  scope: 'string',
  	  begin: '\'',
  	  end: '\'',
  	  illegal: '\\n',
  	  contains: [BACKSLASH_ESCAPE]
  	};
  	const QUOTE_STRING_MODE = {
  	  scope: 'string',
  	  begin: '"',
  	  end: '"',
  	  illegal: '\\n',
  	  contains: [BACKSLASH_ESCAPE]
  	};
  	const PHRASAL_WORDS_MODE = {
  	  begin: /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/
  	};
  	/**
  	 * Creates a comment mode
  	 *
  	 * @param {string | RegExp} begin
  	 * @param {string | RegExp} end
  	 * @param {Mode | {}} [modeOptions]
  	 * @returns {Partial<Mode>}
  	 */
  	const COMMENT = function(begin, end, modeOptions = {}) {
  	  const mode = inherit$1(
  	    {
  	      scope: 'comment',
  	      begin,
  	      end,
  	      contains: []
  	    },
  	    modeOptions
  	  );
  	  mode.contains.push({
  	    scope: 'doctag',
  	    // hack to avoid the space from being included. the space is necessary to
  	    // match here to prevent the plain text rule below from gobbling up doctags
  	    begin: '[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)',
  	    end: /(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,
  	    excludeBegin: true,
  	    relevance: 0
  	  });
  	  const ENGLISH_WORD = either(
  	    // list of common 1 and 2 letter words in English
  	    "I",
  	    "a",
  	    "is",
  	    "so",
  	    "us",
  	    "to",
  	    "at",
  	    "if",
  	    "in",
  	    "it",
  	    "on",
  	    // note: this is not an exhaustive list of contractions, just popular ones
  	    /[A-Za-z]+['](d|ve|re|ll|t|s|n)/, // contractions - can't we'd they're let's, etc
  	    /[A-Za-z]+[-][a-z]+/, // `no-way`, etc.
  	    /[A-Za-z][a-z]{2,}/ // allow capitalized words at beginning of sentences
  	  );
  	  // looking like plain text, more likely to be a comment
  	  mode.contains.push(
  	    {
  	      // TODO: how to include ", (, ) without breaking grammars that use these for
  	      // comment delimiters?
  	      // begin: /[ ]+([()"]?([A-Za-z'-]{3,}|is|a|I|so|us|[tT][oO]|at|if|in|it|on)[.]?[()":]?([.][ ]|[ ]|\))){3}/
  	      // ---

  	      // this tries to find sequences of 3 english words in a row (without any
  	      // "programming" type syntax) this gives us a strong signal that we've
  	      // TRULY found a comment - vs perhaps scanning with the wrong language.
  	      // It's possible to find something that LOOKS like the start of the
  	      // comment - but then if there is no readable text - good chance it is a
  	      // false match and not a comment.
  	      //
  	      // for a visual example please see:
  	      // https://github.com/highlightjs/highlight.js/issues/2827

  	      begin: concat(
  	        /[ ]+/, // necessary to prevent us gobbling up doctags like /* @author Bob Mcgill */
  	        '(',
  	        ENGLISH_WORD,
  	        /[.]?[:]?([.][ ]|[ ])/,
  	        '){3}') // look for 3 words in a row
  	    }
  	  );
  	  return mode;
  	};
  	const C_LINE_COMMENT_MODE = COMMENT('//', '$');
  	const C_BLOCK_COMMENT_MODE = COMMENT('/\\*', '\\*/');
  	const HASH_COMMENT_MODE = COMMENT('#', '$');
  	const NUMBER_MODE = {
  	  scope: 'number',
  	  begin: NUMBER_RE,
  	  relevance: 0
  	};
  	const C_NUMBER_MODE = {
  	  scope: 'number',
  	  begin: C_NUMBER_RE,
  	  relevance: 0
  	};
  	const BINARY_NUMBER_MODE = {
  	  scope: 'number',
  	  begin: BINARY_NUMBER_RE,
  	  relevance: 0
  	};
  	const REGEXP_MODE = {
  	  scope: "regexp",
  	  begin: /\/(?=[^/\n]*\/)/,
  	  end: /\/[gimuy]*/,
  	  contains: [
  	    BACKSLASH_ESCAPE,
  	    {
  	      begin: /\[/,
  	      end: /\]/,
  	      relevance: 0,
  	      contains: [BACKSLASH_ESCAPE]
  	    }
  	  ]
  	};
  	const TITLE_MODE = {
  	  scope: 'title',
  	  begin: IDENT_RE,
  	  relevance: 0
  	};
  	const UNDERSCORE_TITLE_MODE = {
  	  scope: 'title',
  	  begin: UNDERSCORE_IDENT_RE,
  	  relevance: 0
  	};
  	const METHOD_GUARD = {
  	  // excludes method names from keyword processing
  	  begin: '\\.\\s*' + UNDERSCORE_IDENT_RE,
  	  relevance: 0
  	};

  	/**
  	 * Adds end same as begin mechanics to a mode
  	 *
  	 * Your mode must include at least a single () match group as that first match
  	 * group is what is used for comparison
  	 * @param {Partial<Mode>} mode
  	 */
  	const END_SAME_AS_BEGIN = function(mode) {
  	  return Object.assign(mode,
  	    {
  	      /** @type {ModeCallback} */
  	      'on:begin': (m, resp) => { resp.data._beginMatch = m[1]; },
  	      /** @type {ModeCallback} */
  	      'on:end': (m, resp) => { if (resp.data._beginMatch !== m[1]) resp.ignoreMatch(); }
  	    });
  	};

  	var MODES = /*#__PURE__*/Object.freeze({
  	  __proto__: null,
  	  APOS_STRING_MODE: APOS_STRING_MODE,
  	  BACKSLASH_ESCAPE: BACKSLASH_ESCAPE,
  	  BINARY_NUMBER_MODE: BINARY_NUMBER_MODE,
  	  BINARY_NUMBER_RE: BINARY_NUMBER_RE,
  	  COMMENT: COMMENT,
  	  C_BLOCK_COMMENT_MODE: C_BLOCK_COMMENT_MODE,
  	  C_LINE_COMMENT_MODE: C_LINE_COMMENT_MODE,
  	  C_NUMBER_MODE: C_NUMBER_MODE,
  	  C_NUMBER_RE: C_NUMBER_RE,
  	  END_SAME_AS_BEGIN: END_SAME_AS_BEGIN,
  	  HASH_COMMENT_MODE: HASH_COMMENT_MODE,
  	  IDENT_RE: IDENT_RE,
  	  MATCH_NOTHING_RE: MATCH_NOTHING_RE,
  	  METHOD_GUARD: METHOD_GUARD,
  	  NUMBER_MODE: NUMBER_MODE,
  	  NUMBER_RE: NUMBER_RE,
  	  PHRASAL_WORDS_MODE: PHRASAL_WORDS_MODE,
  	  QUOTE_STRING_MODE: QUOTE_STRING_MODE,
  	  REGEXP_MODE: REGEXP_MODE,
  	  RE_STARTERS_RE: RE_STARTERS_RE,
  	  SHEBANG: SHEBANG,
  	  TITLE_MODE: TITLE_MODE,
  	  UNDERSCORE_IDENT_RE: UNDERSCORE_IDENT_RE,
  	  UNDERSCORE_TITLE_MODE: UNDERSCORE_TITLE_MODE
  	});

  	/**
  	@typedef {import('highlight.js').CallbackResponse} CallbackResponse
  	@typedef {import('highlight.js').CompilerExt} CompilerExt
  	*/

  	// Grammar extensions / plugins
  	// See: https://github.com/highlightjs/highlight.js/issues/2833

  	// Grammar extensions allow "syntactic sugar" to be added to the grammar modes
  	// without requiring any underlying changes to the compiler internals.

  	// `compileMatch` being the perfect small example of now allowing a grammar
  	// author to write `match` when they desire to match a single expression rather
  	// than being forced to use `begin`.  The extension then just moves `match` into
  	// `begin` when it runs.  Ie, no features have been added, but we've just made
  	// the experience of writing (and reading grammars) a little bit nicer.

  	// ------

  	// TODO: We need negative look-behind support to do this properly
  	/**
  	 * Skip a match if it has a preceding dot
  	 *
  	 * This is used for `beginKeywords` to prevent matching expressions such as
  	 * `bob.keyword.do()`. The mode compiler automatically wires this up as a
  	 * special _internal_ 'on:begin' callback for modes with `beginKeywords`
  	 * @param {RegExpMatchArray} match
  	 * @param {CallbackResponse} response
  	 */
  	function skipIfHasPrecedingDot(match, response) {
  	  const before = match.input[match.index - 1];
  	  if (before === ".") {
  	    response.ignoreMatch();
  	  }
  	}

  	/**
  	 *
  	 * @type {CompilerExt}
  	 */
  	function scopeClassName(mode, _parent) {
  	  // eslint-disable-next-line no-undefined
  	  if (mode.className !== undefined) {
  	    mode.scope = mode.className;
  	    delete mode.className;
  	  }
  	}

  	/**
  	 * `beginKeywords` syntactic sugar
  	 * @type {CompilerExt}
  	 */
  	function beginKeywords(mode, parent) {
  	  if (!parent) return;
  	  if (!mode.beginKeywords) return;

  	  // for languages with keywords that include non-word characters checking for
  	  // a word boundary is not sufficient, so instead we check for a word boundary
  	  // or whitespace - this does no harm in any case since our keyword engine
  	  // doesn't allow spaces in keywords anyways and we still check for the boundary
  	  // first
  	  mode.begin = '\\b(' + mode.beginKeywords.split(' ').join('|') + ')(?!\\.)(?=\\b|\\s)';
  	  mode.__beforeBegin = skipIfHasPrecedingDot;
  	  mode.keywords = mode.keywords || mode.beginKeywords;
  	  delete mode.beginKeywords;

  	  // prevents double relevance, the keywords themselves provide
  	  // relevance, the mode doesn't need to double it
  	  // eslint-disable-next-line no-undefined
  	  if (mode.relevance === undefined) mode.relevance = 0;
  	}

  	/**
  	 * Allow `illegal` to contain an array of illegal values
  	 * @type {CompilerExt}
  	 */
  	function compileIllegal(mode, _parent) {
  	  if (!Array.isArray(mode.illegal)) return;

  	  mode.illegal = either(...mode.illegal);
  	}

  	/**
  	 * `match` to match a single expression for readability
  	 * @type {CompilerExt}
  	 */
  	function compileMatch(mode, _parent) {
  	  if (!mode.match) return;
  	  if (mode.begin || mode.end) throw new Error("begin & end are not supported with match");

  	  mode.begin = mode.match;
  	  delete mode.match;
  	}

  	/**
  	 * provides the default 1 relevance to all modes
  	 * @type {CompilerExt}
  	 */
  	function compileRelevance(mode, _parent) {
  	  // eslint-disable-next-line no-undefined
  	  if (mode.relevance === undefined) mode.relevance = 1;
  	}

  	// allow beforeMatch to act as a "qualifier" for the match
  	// the full match begin must be [beforeMatch][begin]
  	const beforeMatchExt = (mode, parent) => {
  	  if (!mode.beforeMatch) return;
  	  // starts conflicts with endsParent which we need to make sure the child
  	  // rule is not matched multiple times
  	  if (mode.starts) throw new Error("beforeMatch cannot be used with starts");

  	  const originalMode = Object.assign({}, mode);
  	  Object.keys(mode).forEach((key) => { delete mode[key]; });

  	  mode.keywords = originalMode.keywords;
  	  mode.begin = concat(originalMode.beforeMatch, lookahead(originalMode.begin));
  	  mode.starts = {
  	    relevance: 0,
  	    contains: [
  	      Object.assign(originalMode, { endsParent: true })
  	    ]
  	  };
  	  mode.relevance = 0;

  	  delete originalMode.beforeMatch;
  	};

  	// keywords that should have no default relevance value
  	const COMMON_KEYWORDS = [
  	  'of',
  	  'and',
  	  'for',
  	  'in',
  	  'not',
  	  'or',
  	  'if',
  	  'then',
  	  'parent', // common variable name
  	  'list', // common variable name
  	  'value' // common variable name
  	];

  	const DEFAULT_KEYWORD_SCOPE = "keyword";

  	/**
  	 * Given raw keywords from a language definition, compile them.
  	 *
  	 * @param {string | Record<string,string|string[]> | Array<string>} rawKeywords
  	 * @param {boolean} caseInsensitive
  	 */
  	function compileKeywords(rawKeywords, caseInsensitive, scopeName = DEFAULT_KEYWORD_SCOPE) {
  	  /** @type {import("highlight.js/private").KeywordDict} */
  	  const compiledKeywords = Object.create(null);

  	  // input can be a string of keywords, an array of keywords, or a object with
  	  // named keys representing scopeName (which can then point to a string or array)
  	  if (typeof rawKeywords === 'string') {
  	    compileList(scopeName, rawKeywords.split(" "));
  	  } else if (Array.isArray(rawKeywords)) {
  	    compileList(scopeName, rawKeywords);
  	  } else {
  	    Object.keys(rawKeywords).forEach(function(scopeName) {
  	      // collapse all our objects back into the parent object
  	      Object.assign(
  	        compiledKeywords,
  	        compileKeywords(rawKeywords[scopeName], caseInsensitive, scopeName)
  	      );
  	    });
  	  }
  	  return compiledKeywords;

  	  // ---

  	  /**
  	   * Compiles an individual list of keywords
  	   *
  	   * Ex: "for if when while|5"
  	   *
  	   * @param {string} scopeName
  	   * @param {Array<string>} keywordList
  	   */
  	  function compileList(scopeName, keywordList) {
  	    if (caseInsensitive) {
  	      keywordList = keywordList.map(x => x.toLowerCase());
  	    }
  	    keywordList.forEach(function(keyword) {
  	      const pair = keyword.split('|');
  	      compiledKeywords[pair[0]] = [scopeName, scoreForKeyword(pair[0], pair[1])];
  	    });
  	  }
  	}

  	/**
  	 * Returns the proper score for a given keyword
  	 *
  	 * Also takes into account comment keywords, which will be scored 0 UNLESS
  	 * another score has been manually assigned.
  	 * @param {string} keyword
  	 * @param {string} [providedScore]
  	 */
  	function scoreForKeyword(keyword, providedScore) {
  	  // manual scores always win over common keywords
  	  // so you can force a score of 1 if you really insist
  	  if (providedScore) {
  	    return Number(providedScore);
  	  }

  	  return commonKeyword(keyword) ? 0 : 1;
  	}

  	/**
  	 * Determines if a given keyword is common or not
  	 *
  	 * @param {string} keyword */
  	function commonKeyword(keyword) {
  	  return COMMON_KEYWORDS.includes(keyword.toLowerCase());
  	}

  	/*

  	For the reasoning behind this please see:
  	https://github.com/highlightjs/highlight.js/issues/2880#issuecomment-747275419

  	*/

  	/**
  	 * @type {Record<string, boolean>}
  	 */
  	const seenDeprecations = {};

  	/**
  	 * @param {string} message
  	 */
  	const error = (message) => {
  	  console.error(message);
  	};

  	/**
  	 * @param {string} message
  	 * @param {any} args
  	 */
  	const warn = (message, ...args) => {
  	  console.log(`WARN: ${message}`, ...args);
  	};

  	/**
  	 * @param {string} version
  	 * @param {string} message
  	 */
  	const deprecated = (version, message) => {
  	  if (seenDeprecations[`${version}/${message}`]) return;

  	  console.log(`Deprecated as of ${version}. ${message}`);
  	  seenDeprecations[`${version}/${message}`] = true;
  	};

  	/* eslint-disable no-throw-literal */

  	/**
  	@typedef {import('highlight.js').CompiledMode} CompiledMode
  	*/

  	const MultiClassError = new Error();

  	/**
  	 * Renumbers labeled scope names to account for additional inner match
  	 * groups that otherwise would break everything.
  	 *
  	 * Lets say we 3 match scopes:
  	 *
  	 *   { 1 => ..., 2 => ..., 3 => ... }
  	 *
  	 * So what we need is a clean match like this:
  	 *
  	 *   (a)(b)(c) => [ "a", "b", "c" ]
  	 *
  	 * But this falls apart with inner match groups:
  	 *
  	 * (a)(((b)))(c) => ["a", "b", "b", "b", "c" ]
  	 *
  	 * Our scopes are now "out of alignment" and we're repeating `b` 3 times.
  	 * What needs to happen is the numbers are remapped:
  	 *
  	 *   { 1 => ..., 2 => ..., 5 => ... }
  	 *
  	 * We also need to know that the ONLY groups that should be output
  	 * are 1, 2, and 5.  This function handles this behavior.
  	 *
  	 * @param {CompiledMode} mode
  	 * @param {Array<RegExp | string>} regexes
  	 * @param {{key: "beginScope"|"endScope"}} opts
  	 */
  	function remapScopeNames(mode, regexes, { key }) {
  	  let offset = 0;
  	  const scopeNames = mode[key];
  	  /** @type Record<number,boolean> */
  	  const emit = {};
  	  /** @type Record<number,string> */
  	  const positions = {};

  	  for (let i = 1; i <= regexes.length; i++) {
  	    positions[i + offset] = scopeNames[i];
  	    emit[i + offset] = true;
  	    offset += countMatchGroups(regexes[i - 1]);
  	  }
  	  // we use _emit to keep track of which match groups are "top-level" to avoid double
  	  // output from inside match groups
  	  mode[key] = positions;
  	  mode[key]._emit = emit;
  	  mode[key]._multi = true;
  	}

  	/**
  	 * @param {CompiledMode} mode
  	 */
  	function beginMultiClass(mode) {
  	  if (!Array.isArray(mode.begin)) return;

  	  if (mode.skip || mode.excludeBegin || mode.returnBegin) {
  	    error("skip, excludeBegin, returnBegin not compatible with beginScope: {}");
  	    throw MultiClassError;
  	  }

  	  if (typeof mode.beginScope !== "object" || mode.beginScope === null) {
  	    error("beginScope must be object");
  	    throw MultiClassError;
  	  }

  	  remapScopeNames(mode, mode.begin, { key: "beginScope" });
  	  mode.begin = _rewriteBackreferences(mode.begin, { joinWith: "" });
  	}

  	/**
  	 * @param {CompiledMode} mode
  	 */
  	function endMultiClass(mode) {
  	  if (!Array.isArray(mode.end)) return;

  	  if (mode.skip || mode.excludeEnd || mode.returnEnd) {
  	    error("skip, excludeEnd, returnEnd not compatible with endScope: {}");
  	    throw MultiClassError;
  	  }

  	  if (typeof mode.endScope !== "object" || mode.endScope === null) {
  	    error("endScope must be object");
  	    throw MultiClassError;
  	  }

  	  remapScopeNames(mode, mode.end, { key: "endScope" });
  	  mode.end = _rewriteBackreferences(mode.end, { joinWith: "" });
  	}

  	/**
  	 * this exists only to allow `scope: {}` to be used beside `match:`
  	 * Otherwise `beginScope` would necessary and that would look weird

  	  {
  	    match: [ /def/, /\w+/ ]
  	    scope: { 1: "keyword" , 2: "title" }
  	  }

  	 * @param {CompiledMode} mode
  	 */
  	function scopeSugar(mode) {
  	  if (mode.scope && typeof mode.scope === "object" && mode.scope !== null) {
  	    mode.beginScope = mode.scope;
  	    delete mode.scope;
  	  }
  	}

  	/**
  	 * @param {CompiledMode} mode
  	 */
  	function MultiClass(mode) {
  	  scopeSugar(mode);

  	  if (typeof mode.beginScope === "string") {
  	    mode.beginScope = { _wrap: mode.beginScope };
  	  }
  	  if (typeof mode.endScope === "string") {
  	    mode.endScope = { _wrap: mode.endScope };
  	  }

  	  beginMultiClass(mode);
  	  endMultiClass(mode);
  	}

  	/**
  	@typedef {import('highlight.js').Mode} Mode
  	@typedef {import('highlight.js').CompiledMode} CompiledMode
  	@typedef {import('highlight.js').Language} Language
  	@typedef {import('highlight.js').HLJSPlugin} HLJSPlugin
  	@typedef {import('highlight.js').CompiledLanguage} CompiledLanguage
  	*/

  	// compilation

  	/**
  	 * Compiles a language definition result
  	 *
  	 * Given the raw result of a language definition (Language), compiles this so
  	 * that it is ready for highlighting code.
  	 * @param {Language} language
  	 * @returns {CompiledLanguage}
  	 */
  	function compileLanguage(language) {
  	  /**
  	   * Builds a regex with the case sensitivity of the current language
  	   *
  	   * @param {RegExp | string} value
  	   * @param {boolean} [global]
  	   */
  	  function langRe(value, global) {
  	    return new RegExp(
  	      source(value),
  	      'm'
  	      + (language.case_insensitive ? 'i' : '')
  	      + (language.unicodeRegex ? 'u' : '')
  	      + (global ? 'g' : '')
  	    );
  	  }

  	  /**
  	    Stores multiple regular expressions and allows you to quickly search for
  	    them all in a string simultaneously - returning the first match.  It does
  	    this by creating a huge (a|b|c) regex - each individual item wrapped with ()
  	    and joined by `|` - using match groups to track position.  When a match is
  	    found checking which position in the array has content allows us to figure
  	    out which of the original regexes / match groups triggered the match.

  	    The match object itself (the result of `Regex.exec`) is returned but also
  	    enhanced by merging in any meta-data that was registered with the regex.
  	    This is how we keep track of which mode matched, and what type of rule
  	    (`illegal`, `begin`, end, etc).
  	  */
  	  class MultiRegex {
  	    constructor() {
  	      this.matchIndexes = {};
  	      // @ts-ignore
  	      this.regexes = [];
  	      this.matchAt = 1;
  	      this.position = 0;
  	    }

  	    // @ts-ignore
  	    addRule(re, opts) {
  	      opts.position = this.position++;
  	      // @ts-ignore
  	      this.matchIndexes[this.matchAt] = opts;
  	      this.regexes.push([opts, re]);
  	      this.matchAt += countMatchGroups(re) + 1;
  	    }

  	    compile() {
  	      if (this.regexes.length === 0) {
  	        // avoids the need to check length every time exec is called
  	        // @ts-ignore
  	        this.exec = () => null;
  	      }
  	      const terminators = this.regexes.map(el => el[1]);
  	      this.matcherRe = langRe(_rewriteBackreferences(terminators, { joinWith: '|' }), true);
  	      this.lastIndex = 0;
  	    }

  	    /** @param {string} s */
  	    exec(s) {
  	      this.matcherRe.lastIndex = this.lastIndex;
  	      const match = this.matcherRe.exec(s);
  	      if (!match) { return null; }

  	      // eslint-disable-next-line no-undefined
  	      const i = match.findIndex((el, i) => i > 0 && el !== undefined);
  	      // @ts-ignore
  	      const matchData = this.matchIndexes[i];
  	      // trim off any earlier non-relevant match groups (ie, the other regex
  	      // match groups that make up the multi-matcher)
  	      match.splice(0, i);

  	      return Object.assign(match, matchData);
  	    }
  	  }

  	  /*
  	    Created to solve the key deficiently with MultiRegex - there is no way to
  	    test for multiple matches at a single location.  Why would we need to do
  	    that?  In the future a more dynamic engine will allow certain matches to be
  	    ignored.  An example: if we matched say the 3rd regex in a large group but
  	    decided to ignore it - we'd need to started testing again at the 4th
  	    regex... but MultiRegex itself gives us no real way to do that.

  	    So what this class creates MultiRegexs on the fly for whatever search
  	    position they are needed.

  	    NOTE: These additional MultiRegex objects are created dynamically.  For most
  	    grammars most of the time we will never actually need anything more than the
  	    first MultiRegex - so this shouldn't have too much overhead.

  	    Say this is our search group, and we match regex3, but wish to ignore it.

  	      regex1 | regex2 | regex3 | regex4 | regex5    ' ie, startAt = 0

  	    What we need is a new MultiRegex that only includes the remaining
  	    possibilities:

  	      regex4 | regex5                               ' ie, startAt = 3

  	    This class wraps all that complexity up in a simple API... `startAt` decides
  	    where in the array of expressions to start doing the matching. It
  	    auto-increments, so if a match is found at position 2, then startAt will be
  	    set to 3.  If the end is reached startAt will return to 0.

  	    MOST of the time the parser will be setting startAt manually to 0.
  	  */
  	  class ResumableMultiRegex {
  	    constructor() {
  	      // @ts-ignore
  	      this.rules = [];
  	      // @ts-ignore
  	      this.multiRegexes = [];
  	      this.count = 0;

  	      this.lastIndex = 0;
  	      this.regexIndex = 0;
  	    }

  	    // @ts-ignore
  	    getMatcher(index) {
  	      if (this.multiRegexes[index]) return this.multiRegexes[index];

  	      const matcher = new MultiRegex();
  	      this.rules.slice(index).forEach(([re, opts]) => matcher.addRule(re, opts));
  	      matcher.compile();
  	      this.multiRegexes[index] = matcher;
  	      return matcher;
  	    }

  	    resumingScanAtSamePosition() {
  	      return this.regexIndex !== 0;
  	    }

  	    considerAll() {
  	      this.regexIndex = 0;
  	    }

  	    // @ts-ignore
  	    addRule(re, opts) {
  	      this.rules.push([re, opts]);
  	      if (opts.type === "begin") this.count++;
  	    }

  	    /** @param {string} s */
  	    exec(s) {
  	      const m = this.getMatcher(this.regexIndex);
  	      m.lastIndex = this.lastIndex;
  	      let result = m.exec(s);

  	      // The following is because we have no easy way to say "resume scanning at the
  	      // existing position but also skip the current rule ONLY". What happens is
  	      // all prior rules are also skipped which can result in matching the wrong
  	      // thing. Example of matching "booger":

  	      // our matcher is [string, "booger", number]
  	      //
  	      // ....booger....

  	      // if "booger" is ignored then we'd really need a regex to scan from the
  	      // SAME position for only: [string, number] but ignoring "booger" (if it
  	      // was the first match), a simple resume would scan ahead who knows how
  	      // far looking only for "number", ignoring potential string matches (or
  	      // future "booger" matches that might be valid.)

  	      // So what we do: We execute two matchers, one resuming at the same
  	      // position, but the second full matcher starting at the position after:

  	      //     /--- resume first regex match here (for [number])
  	      //     |/---- full match here for [string, "booger", number]
  	      //     vv
  	      // ....booger....

  	      // Which ever results in a match first is then used. So this 3-4 step
  	      // process essentially allows us to say "match at this position, excluding
  	      // a prior rule that was ignored".
  	      //
  	      // 1. Match "booger" first, ignore. Also proves that [string] does non match.
  	      // 2. Resume matching for [number]
  	      // 3. Match at index + 1 for [string, "booger", number]
  	      // 4. If #2 and #3 result in matches, which came first?
  	      if (this.resumingScanAtSamePosition()) {
  	        if (result && result.index === this.lastIndex) ; else { // use the second matcher result
  	          const m2 = this.getMatcher(0);
  	          m2.lastIndex = this.lastIndex + 1;
  	          result = m2.exec(s);
  	        }
  	      }

  	      if (result) {
  	        this.regexIndex += result.position + 1;
  	        if (this.regexIndex === this.count) {
  	          // wrap-around to considering all matches again
  	          this.considerAll();
  	        }
  	      }

  	      return result;
  	    }
  	  }

  	  /**
  	   * Given a mode, builds a huge ResumableMultiRegex that can be used to walk
  	   * the content and find matches.
  	   *
  	   * @param {CompiledMode} mode
  	   * @returns {ResumableMultiRegex}
  	   */
  	  function buildModeRegex(mode) {
  	    const mm = new ResumableMultiRegex();

  	    mode.contains.forEach(term => mm.addRule(term.begin, { rule: term, type: "begin" }));

  	    if (mode.terminatorEnd) {
  	      mm.addRule(mode.terminatorEnd, { type: "end" });
  	    }
  	    if (mode.illegal) {
  	      mm.addRule(mode.illegal, { type: "illegal" });
  	    }

  	    return mm;
  	  }

  	  /** skip vs abort vs ignore
  	   *
  	   * @skip   - The mode is still entered and exited normally (and contains rules apply),
  	   *           but all content is held and added to the parent buffer rather than being
  	   *           output when the mode ends.  Mostly used with `sublanguage` to build up
  	   *           a single large buffer than can be parsed by sublanguage.
  	   *
  	   *             - The mode begin ands ends normally.
  	   *             - Content matched is added to the parent mode buffer.
  	   *             - The parser cursor is moved forward normally.
  	   *
  	   * @abort  - A hack placeholder until we have ignore.  Aborts the mode (as if it
  	   *           never matched) but DOES NOT continue to match subsequent `contains`
  	   *           modes.  Abort is bad/suboptimal because it can result in modes
  	   *           farther down not getting applied because an earlier rule eats the
  	   *           content but then aborts.
  	   *
  	   *             - The mode does not begin.
  	   *             - Content matched by `begin` is added to the mode buffer.
  	   *             - The parser cursor is moved forward accordingly.
  	   *
  	   * @ignore - Ignores the mode (as if it never matched) and continues to match any
  	   *           subsequent `contains` modes.  Ignore isn't technically possible with
  	   *           the current parser implementation.
  	   *
  	   *             - The mode does not begin.
  	   *             - Content matched by `begin` is ignored.
  	   *             - The parser cursor is not moved forward.
  	   */

  	  /**
  	   * Compiles an individual mode
  	   *
  	   * This can raise an error if the mode contains certain detectable known logic
  	   * issues.
  	   * @param {Mode} mode
  	   * @param {CompiledMode | null} [parent]
  	   * @returns {CompiledMode | never}
  	   */
  	  function compileMode(mode, parent) {
  	    const cmode = /** @type CompiledMode */ (mode);
  	    if (mode.isCompiled) return cmode;

  	    [
  	      scopeClassName,
  	      // do this early so compiler extensions generally don't have to worry about
  	      // the distinction between match/begin
  	      compileMatch,
  	      MultiClass,
  	      beforeMatchExt
  	    ].forEach(ext => ext(mode, parent));

  	    language.compilerExtensions.forEach(ext => ext(mode, parent));

  	    // __beforeBegin is considered private API, internal use only
  	    mode.__beforeBegin = null;

  	    [
  	      beginKeywords,
  	      // do this later so compiler extensions that come earlier have access to the
  	      // raw array if they wanted to perhaps manipulate it, etc.
  	      compileIllegal,
  	      // default to 1 relevance if not specified
  	      compileRelevance
  	    ].forEach(ext => ext(mode, parent));

  	    mode.isCompiled = true;

  	    let keywordPattern = null;
  	    if (typeof mode.keywords === "object" && mode.keywords.$pattern) {
  	      // we need a copy because keywords might be compiled multiple times
  	      // so we can't go deleting $pattern from the original on the first
  	      // pass
  	      mode.keywords = Object.assign({}, mode.keywords);
  	      keywordPattern = mode.keywords.$pattern;
  	      delete mode.keywords.$pattern;
  	    }
  	    keywordPattern = keywordPattern || /\w+/;

  	    if (mode.keywords) {
  	      mode.keywords = compileKeywords(mode.keywords, language.case_insensitive);
  	    }

  	    cmode.keywordPatternRe = langRe(keywordPattern, true);

  	    if (parent) {
  	      if (!mode.begin) mode.begin = /\B|\b/;
  	      cmode.beginRe = langRe(cmode.begin);
  	      if (!mode.end && !mode.endsWithParent) mode.end = /\B|\b/;
  	      if (mode.end) cmode.endRe = langRe(cmode.end);
  	      cmode.terminatorEnd = source(cmode.end) || '';
  	      if (mode.endsWithParent && parent.terminatorEnd) {
  	        cmode.terminatorEnd += (mode.end ? '|' : '') + parent.terminatorEnd;
  	      }
  	    }
  	    if (mode.illegal) cmode.illegalRe = langRe(/** @type {RegExp | string} */ (mode.illegal));
  	    if (!mode.contains) mode.contains = [];

  	    mode.contains = [].concat(...mode.contains.map(function(c) {
  	      return expandOrCloneMode(c === 'self' ? mode : c);
  	    }));
  	    mode.contains.forEach(function(c) { compileMode(/** @type Mode */ (c), cmode); });

  	    if (mode.starts) {
  	      compileMode(mode.starts, parent);
  	    }

  	    cmode.matcher = buildModeRegex(cmode);
  	    return cmode;
  	  }

  	  if (!language.compilerExtensions) language.compilerExtensions = [];

  	  // self is not valid at the top-level
  	  if (language.contains && language.contains.includes('self')) {
  	    throw new Error("ERR: contains `self` is not supported at the top-level of a language.  See documentation.");
  	  }

  	  // we need a null object, which inherit will guarantee
  	  language.classNameAliases = inherit$1(language.classNameAliases || {});

  	  return compileMode(/** @type Mode */ (language));
  	}

  	/**
  	 * Determines if a mode has a dependency on it's parent or not
  	 *
  	 * If a mode does have a parent dependency then often we need to clone it if
  	 * it's used in multiple places so that each copy points to the correct parent,
  	 * where-as modes without a parent can often safely be re-used at the bottom of
  	 * a mode chain.
  	 *
  	 * @param {Mode | null} mode
  	 * @returns {boolean} - is there a dependency on the parent?
  	 * */
  	function dependencyOnParent(mode) {
  	  if (!mode) return false;

  	  return mode.endsWithParent || dependencyOnParent(mode.starts);
  	}

  	/**
  	 * Expands a mode or clones it if necessary
  	 *
  	 * This is necessary for modes with parental dependenceis (see notes on
  	 * `dependencyOnParent`) and for nodes that have `variants` - which must then be
  	 * exploded into their own individual modes at compile time.
  	 *
  	 * @param {Mode} mode
  	 * @returns {Mode | Mode[]}
  	 * */
  	function expandOrCloneMode(mode) {
  	  if (mode.variants && !mode.cachedVariants) {
  	    mode.cachedVariants = mode.variants.map(function(variant) {
  	      return inherit$1(mode, { variants: null }, variant);
  	    });
  	  }

  	  // EXPAND
  	  // if we have variants then essentially "replace" the mode with the variants
  	  // this happens in compileMode, where this function is called from
  	  if (mode.cachedVariants) {
  	    return mode.cachedVariants;
  	  }

  	  // CLONE
  	  // if we have dependencies on parents then we need a unique
  	  // instance of ourselves, so we can be reused with many
  	  // different parents without issue
  	  if (dependencyOnParent(mode)) {
  	    return inherit$1(mode, { starts: mode.starts ? inherit$1(mode.starts) : null });
  	  }

  	  if (Object.isFrozen(mode)) {
  	    return inherit$1(mode);
  	  }

  	  // no special dependency issues, just return ourselves
  	  return mode;
  	}

  	var version = "11.11.1";

  	class HTMLInjectionError extends Error {
  	  constructor(reason, html) {
  	    super(reason);
  	    this.name = "HTMLInjectionError";
  	    this.html = html;
  	  }
  	}

  	/*
  	Syntax highlighting with language autodetection.
  	https://highlightjs.org/
  	*/



  	/**
  	@typedef {import('highlight.js').Mode} Mode
  	@typedef {import('highlight.js').CompiledMode} CompiledMode
  	@typedef {import('highlight.js').CompiledScope} CompiledScope
  	@typedef {import('highlight.js').Language} Language
  	@typedef {import('highlight.js').HLJSApi} HLJSApi
  	@typedef {import('highlight.js').HLJSPlugin} HLJSPlugin
  	@typedef {import('highlight.js').PluginEvent} PluginEvent
  	@typedef {import('highlight.js').HLJSOptions} HLJSOptions
  	@typedef {import('highlight.js').LanguageFn} LanguageFn
  	@typedef {import('highlight.js').HighlightedHTMLElement} HighlightedHTMLElement
  	@typedef {import('highlight.js').BeforeHighlightContext} BeforeHighlightContext
  	@typedef {import('highlight.js/private').MatchType} MatchType
  	@typedef {import('highlight.js/private').KeywordData} KeywordData
  	@typedef {import('highlight.js/private').EnhancedMatch} EnhancedMatch
  	@typedef {import('highlight.js/private').AnnotatedError} AnnotatedError
  	@typedef {import('highlight.js').AutoHighlightResult} AutoHighlightResult
  	@typedef {import('highlight.js').HighlightOptions} HighlightOptions
  	@typedef {import('highlight.js').HighlightResult} HighlightResult
  	*/


  	const escape = escapeHTML;
  	const inherit = inherit$1;
  	const NO_MATCH = Symbol("nomatch");
  	const MAX_KEYWORD_HITS = 7;

  	/**
  	 * @param {any} hljs - object that is extended (legacy)
  	 * @returns {HLJSApi}
  	 */
  	const HLJS = function(hljs) {
  	  // Global internal variables used within the highlight.js library.
  	  /** @type {Record<string, Language>} */
  	  const languages = Object.create(null);
  	  /** @type {Record<string, string>} */
  	  const aliases = Object.create(null);
  	  /** @type {HLJSPlugin[]} */
  	  const plugins = [];

  	  // safe/production mode - swallows more errors, tries to keep running
  	  // even if a single syntax or parse hits a fatal error
  	  let SAFE_MODE = true;
  	  const LANGUAGE_NOT_FOUND = "Could not find the language '{}', did you forget to load/include a language module?";
  	  /** @type {Language} */
  	  const PLAINTEXT_LANGUAGE = { disableAutodetect: true, name: 'Plain text', contains: [] };

  	  // Global options used when within external APIs. This is modified when
  	  // calling the `hljs.configure` function.
  	  /** @type HLJSOptions */
  	  let options = {
  	    ignoreUnescapedHTML: false,
  	    throwUnescapedHTML: false,
  	    noHighlightRe: /^(no-?highlight)$/i,
  	    languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i,
  	    classPrefix: 'hljs-',
  	    cssSelector: 'pre code',
  	    languages: null,
  	    // beta configuration options, subject to change, welcome to discuss
  	    // https://github.com/highlightjs/highlight.js/issues/1086
  	    __emitter: TokenTreeEmitter
  	  };

  	  /* Utility functions */

  	  /**
  	   * Tests a language name to see if highlighting should be skipped
  	   * @param {string} languageName
  	   */
  	  function shouldNotHighlight(languageName) {
  	    return options.noHighlightRe.test(languageName);
  	  }

  	  /**
  	   * @param {HighlightedHTMLElement} block - the HTML element to determine language for
  	   */
  	  function blockLanguage(block) {
  	    let classes = block.className + ' ';

  	    classes += block.parentNode ? block.parentNode.className : '';

  	    // language-* takes precedence over non-prefixed class names.
  	    const match = options.languageDetectRe.exec(classes);
  	    if (match) {
  	      const language = getLanguage(match[1]);
  	      if (!language) {
  	        warn(LANGUAGE_NOT_FOUND.replace("{}", match[1]));
  	        warn("Falling back to no-highlight mode for this block.", block);
  	      }
  	      return language ? match[1] : 'no-highlight';
  	    }

  	    return classes
  	      .split(/\s+/)
  	      .find((_class) => shouldNotHighlight(_class) || getLanguage(_class));
  	  }

  	  /**
  	   * Core highlighting function.
  	   *
  	   * OLD API
  	   * highlight(lang, code, ignoreIllegals, continuation)
  	   *
  	   * NEW API
  	   * highlight(code, {lang, ignoreIllegals})
  	   *
  	   * @param {string} codeOrLanguageName - the language to use for highlighting
  	   * @param {string | HighlightOptions} optionsOrCode - the code to highlight
  	   * @param {boolean} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
  	   *
  	   * @returns {HighlightResult} Result - an object that represents the result
  	   * @property {string} language - the language name
  	   * @property {number} relevance - the relevance score
  	   * @property {string} value - the highlighted HTML code
  	   * @property {string} code - the original raw code
  	   * @property {CompiledMode} top - top of the current mode stack
  	   * @property {boolean} illegal - indicates whether any illegal matches were found
  	  */
  	  function highlight(codeOrLanguageName, optionsOrCode, ignoreIllegals) {
  	    let code = "";
  	    let languageName = "";
  	    if (typeof optionsOrCode === "object") {
  	      code = codeOrLanguageName;
  	      ignoreIllegals = optionsOrCode.ignoreIllegals;
  	      languageName = optionsOrCode.language;
  	    } else {
  	      // old API
  	      deprecated("10.7.0", "highlight(lang, code, ...args) has been deprecated.");
  	      deprecated("10.7.0", "Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277");
  	      languageName = codeOrLanguageName;
  	      code = optionsOrCode;
  	    }

  	    // https://github.com/highlightjs/highlight.js/issues/3149
  	    // eslint-disable-next-line no-undefined
  	    if (ignoreIllegals === undefined) { ignoreIllegals = true; }

  	    /** @type {BeforeHighlightContext} */
  	    const context = {
  	      code,
  	      language: languageName
  	    };
  	    // the plugin can change the desired language or the code to be highlighted
  	    // just be changing the object it was passed
  	    fire("before:highlight", context);

  	    // a before plugin can usurp the result completely by providing it's own
  	    // in which case we don't even need to call highlight
  	    const result = context.result
  	      ? context.result
  	      : _highlight(context.language, context.code, ignoreIllegals);

  	    result.code = context.code;
  	    // the plugin can change anything in result to suite it
  	    fire("after:highlight", result);

  	    return result;
  	  }

  	  /**
  	   * private highlight that's used internally and does not fire callbacks
  	   *
  	   * @param {string} languageName - the language to use for highlighting
  	   * @param {string} codeToHighlight - the code to highlight
  	   * @param {boolean?} [ignoreIllegals] - whether to ignore illegal matches, default is to bail
  	   * @param {CompiledMode?} [continuation] - current continuation mode, if any
  	   * @returns {HighlightResult} - result of the highlight operation
  	  */
  	  function _highlight(languageName, codeToHighlight, ignoreIllegals, continuation) {
  	    const keywordHits = Object.create(null);

  	    /**
  	     * Return keyword data if a match is a keyword
  	     * @param {CompiledMode} mode - current mode
  	     * @param {string} matchText - the textual match
  	     * @returns {KeywordData | false}
  	     */
  	    function keywordData(mode, matchText) {
  	      return mode.keywords[matchText];
  	    }

  	    function processKeywords() {
  	      if (!top.keywords) {
  	        emitter.addText(modeBuffer);
  	        return;
  	      }

  	      let lastIndex = 0;
  	      top.keywordPatternRe.lastIndex = 0;
  	      let match = top.keywordPatternRe.exec(modeBuffer);
  	      let buf = "";

  	      while (match) {
  	        buf += modeBuffer.substring(lastIndex, match.index);
  	        const word = language.case_insensitive ? match[0].toLowerCase() : match[0];
  	        const data = keywordData(top, word);
  	        if (data) {
  	          const [kind, keywordRelevance] = data;
  	          emitter.addText(buf);
  	          buf = "";

  	          keywordHits[word] = (keywordHits[word] || 0) + 1;
  	          if (keywordHits[word] <= MAX_KEYWORD_HITS) relevance += keywordRelevance;
  	          if (kind.startsWith("_")) {
  	            // _ implied for relevance only, do not highlight
  	            // by applying a class name
  	            buf += match[0];
  	          } else {
  	            const cssClass = language.classNameAliases[kind] || kind;
  	            emitKeyword(match[0], cssClass);
  	          }
  	        } else {
  	          buf += match[0];
  	        }
  	        lastIndex = top.keywordPatternRe.lastIndex;
  	        match = top.keywordPatternRe.exec(modeBuffer);
  	      }
  	      buf += modeBuffer.substring(lastIndex);
  	      emitter.addText(buf);
  	    }

  	    function processSubLanguage() {
  	      if (modeBuffer === "") return;
  	      /** @type HighlightResult */
  	      let result = null;

  	      if (typeof top.subLanguage === 'string') {
  	        if (!languages[top.subLanguage]) {
  	          emitter.addText(modeBuffer);
  	          return;
  	        }
  	        result = _highlight(top.subLanguage, modeBuffer, true, continuations[top.subLanguage]);
  	        continuations[top.subLanguage] = /** @type {CompiledMode} */ (result._top);
  	      } else {
  	        result = highlightAuto(modeBuffer, top.subLanguage.length ? top.subLanguage : null);
  	      }

  	      // Counting embedded language score towards the host language may be disabled
  	      // with zeroing the containing mode relevance. Use case in point is Markdown that
  	      // allows XML everywhere and makes every XML snippet to have a much larger Markdown
  	      // score.
  	      if (top.relevance > 0) {
  	        relevance += result.relevance;
  	      }
  	      emitter.__addSublanguage(result._emitter, result.language);
  	    }

  	    function processBuffer() {
  	      if (top.subLanguage != null) {
  	        processSubLanguage();
  	      } else {
  	        processKeywords();
  	      }
  	      modeBuffer = '';
  	    }

  	    /**
  	     * @param {string} text
  	     * @param {string} scope
  	     */
  	    function emitKeyword(keyword, scope) {
  	      if (keyword === "") return;

  	      emitter.startScope(scope);
  	      emitter.addText(keyword);
  	      emitter.endScope();
  	    }

  	    /**
  	     * @param {CompiledScope} scope
  	     * @param {RegExpMatchArray} match
  	     */
  	    function emitMultiClass(scope, match) {
  	      let i = 1;
  	      const max = match.length - 1;
  	      while (i <= max) {
  	        if (!scope._emit[i]) { i++; continue; }
  	        const klass = language.classNameAliases[scope[i]] || scope[i];
  	        const text = match[i];
  	        if (klass) {
  	          emitKeyword(text, klass);
  	        } else {
  	          modeBuffer = text;
  	          processKeywords();
  	          modeBuffer = "";
  	        }
  	        i++;
  	      }
  	    }

  	    /**
  	     * @param {CompiledMode} mode - new mode to start
  	     * @param {RegExpMatchArray} match
  	     */
  	    function startNewMode(mode, match) {
  	      if (mode.scope && typeof mode.scope === "string") {
  	        emitter.openNode(language.classNameAliases[mode.scope] || mode.scope);
  	      }
  	      if (mode.beginScope) {
  	        // beginScope just wraps the begin match itself in a scope
  	        if (mode.beginScope._wrap) {
  	          emitKeyword(modeBuffer, language.classNameAliases[mode.beginScope._wrap] || mode.beginScope._wrap);
  	          modeBuffer = "";
  	        } else if (mode.beginScope._multi) {
  	          // at this point modeBuffer should just be the match
  	          emitMultiClass(mode.beginScope, match);
  	          modeBuffer = "";
  	        }
  	      }

  	      top = Object.create(mode, { parent: { value: top } });
  	      return top;
  	    }

  	    /**
  	     * @param {CompiledMode } mode - the mode to potentially end
  	     * @param {RegExpMatchArray} match - the latest match
  	     * @param {string} matchPlusRemainder - match plus remainder of content
  	     * @returns {CompiledMode | void} - the next mode, or if void continue on in current mode
  	     */
  	    function endOfMode(mode, match, matchPlusRemainder) {
  	      let matched = startsWith(mode.endRe, matchPlusRemainder);

  	      if (matched) {
  	        if (mode["on:end"]) {
  	          const resp = new Response(mode);
  	          mode["on:end"](match, resp);
  	          if (resp.isMatchIgnored) matched = false;
  	        }

  	        if (matched) {
  	          while (mode.endsParent && mode.parent) {
  	            mode = mode.parent;
  	          }
  	          return mode;
  	        }
  	      }
  	      // even if on:end fires an `ignore` it's still possible
  	      // that we might trigger the end node because of a parent mode
  	      if (mode.endsWithParent) {
  	        return endOfMode(mode.parent, match, matchPlusRemainder);
  	      }
  	    }

  	    /**
  	     * Handle matching but then ignoring a sequence of text
  	     *
  	     * @param {string} lexeme - string containing full match text
  	     */
  	    function doIgnore(lexeme) {
  	      if (top.matcher.regexIndex === 0) {
  	        // no more regexes to potentially match here, so we move the cursor forward one
  	        // space
  	        modeBuffer += lexeme[0];
  	        return 1;
  	      } else {
  	        // no need to move the cursor, we still have additional regexes to try and
  	        // match at this very spot
  	        resumeScanAtSamePosition = true;
  	        return 0;
  	      }
  	    }

  	    /**
  	     * Handle the start of a new potential mode match
  	     *
  	     * @param {EnhancedMatch} match - the current match
  	     * @returns {number} how far to advance the parse cursor
  	     */
  	    function doBeginMatch(match) {
  	      const lexeme = match[0];
  	      const newMode = match.rule;

  	      const resp = new Response(newMode);
  	      // first internal before callbacks, then the public ones
  	      const beforeCallbacks = [newMode.__beforeBegin, newMode["on:begin"]];
  	      for (const cb of beforeCallbacks) {
  	        if (!cb) continue;
  	        cb(match, resp);
  	        if (resp.isMatchIgnored) return doIgnore(lexeme);
  	      }

  	      if (newMode.skip) {
  	        modeBuffer += lexeme;
  	      } else {
  	        if (newMode.excludeBegin) {
  	          modeBuffer += lexeme;
  	        }
  	        processBuffer();
  	        if (!newMode.returnBegin && !newMode.excludeBegin) {
  	          modeBuffer = lexeme;
  	        }
  	      }
  	      startNewMode(newMode, match);
  	      return newMode.returnBegin ? 0 : lexeme.length;
  	    }

  	    /**
  	     * Handle the potential end of mode
  	     *
  	     * @param {RegExpMatchArray} match - the current match
  	     */
  	    function doEndMatch(match) {
  	      const lexeme = match[0];
  	      const matchPlusRemainder = codeToHighlight.substring(match.index);

  	      const endMode = endOfMode(top, match, matchPlusRemainder);
  	      if (!endMode) { return NO_MATCH; }

  	      const origin = top;
  	      if (top.endScope && top.endScope._wrap) {
  	        processBuffer();
  	        emitKeyword(lexeme, top.endScope._wrap);
  	      } else if (top.endScope && top.endScope._multi) {
  	        processBuffer();
  	        emitMultiClass(top.endScope, match);
  	      } else if (origin.skip) {
  	        modeBuffer += lexeme;
  	      } else {
  	        if (!(origin.returnEnd || origin.excludeEnd)) {
  	          modeBuffer += lexeme;
  	        }
  	        processBuffer();
  	        if (origin.excludeEnd) {
  	          modeBuffer = lexeme;
  	        }
  	      }
  	      do {
  	        if (top.scope) {
  	          emitter.closeNode();
  	        }
  	        if (!top.skip && !top.subLanguage) {
  	          relevance += top.relevance;
  	        }
  	        top = top.parent;
  	      } while (top !== endMode.parent);
  	      if (endMode.starts) {
  	        startNewMode(endMode.starts, match);
  	      }
  	      return origin.returnEnd ? 0 : lexeme.length;
  	    }

  	    function processContinuations() {
  	      const list = [];
  	      for (let current = top; current !== language; current = current.parent) {
  	        if (current.scope) {
  	          list.unshift(current.scope);
  	        }
  	      }
  	      list.forEach(item => emitter.openNode(item));
  	    }

  	    /** @type {{type?: MatchType, index?: number, rule?: Mode}}} */
  	    let lastMatch = {};

  	    /**
  	     *  Process an individual match
  	     *
  	     * @param {string} textBeforeMatch - text preceding the match (since the last match)
  	     * @param {EnhancedMatch} [match] - the match itself
  	     */
  	    function processLexeme(textBeforeMatch, match) {
  	      const lexeme = match && match[0];

  	      // add non-matched text to the current mode buffer
  	      modeBuffer += textBeforeMatch;

  	      if (lexeme == null) {
  	        processBuffer();
  	        return 0;
  	      }

  	      // we've found a 0 width match and we're stuck, so we need to advance
  	      // this happens when we have badly behaved rules that have optional matchers to the degree that
  	      // sometimes they can end up matching nothing at all
  	      // Ref: https://github.com/highlightjs/highlight.js/issues/2140
  	      if (lastMatch.type === "begin" && match.type === "end" && lastMatch.index === match.index && lexeme === "") {
  	        // spit the "skipped" character that our regex choked on back into the output sequence
  	        modeBuffer += codeToHighlight.slice(match.index, match.index + 1);
  	        if (!SAFE_MODE) {
  	          /** @type {AnnotatedError} */
  	          const err = new Error(`0 width match regex (${languageName})`);
  	          err.languageName = languageName;
  	          err.badRule = lastMatch.rule;
  	          throw err;
  	        }
  	        return 1;
  	      }
  	      lastMatch = match;

  	      if (match.type === "begin") {
  	        return doBeginMatch(match);
  	      } else if (match.type === "illegal" && !ignoreIllegals) {
  	        // illegal match, we do not continue processing
  	        /** @type {AnnotatedError} */
  	        const err = new Error('Illegal lexeme "' + lexeme + '" for mode "' + (top.scope || '<unnamed>') + '"');
  	        err.mode = top;
  	        throw err;
  	      } else if (match.type === "end") {
  	        const processed = doEndMatch(match);
  	        if (processed !== NO_MATCH) {
  	          return processed;
  	        }
  	      }

  	      // edge case for when illegal matches $ (end of line) which is technically
  	      // a 0 width match but not a begin/end match so it's not caught by the
  	      // first handler (when ignoreIllegals is true)
  	      if (match.type === "illegal" && lexeme === "") {
  	        // advance so we aren't stuck in an infinite loop
  	        modeBuffer += "\n";
  	        return 1;
  	      }

  	      // infinite loops are BAD, this is a last ditch catch all. if we have a
  	      // decent number of iterations yet our index (cursor position in our
  	      // parsing) still 3x behind our index then something is very wrong
  	      // so we bail
  	      if (iterations > 100000 && iterations > match.index * 3) {
  	        const err = new Error('potential infinite loop, way more iterations than matches');
  	        throw err;
  	      }

  	      /*
  	      Why might be find ourselves here?  An potential end match that was
  	      triggered but could not be completed.  IE, `doEndMatch` returned NO_MATCH.
  	      (this could be because a callback requests the match be ignored, etc)

  	      This causes no real harm other than stopping a few times too many.
  	      */

  	      modeBuffer += lexeme;
  	      return lexeme.length;
  	    }

  	    const language = getLanguage(languageName);
  	    if (!language) {
  	      error(LANGUAGE_NOT_FOUND.replace("{}", languageName));
  	      throw new Error('Unknown language: "' + languageName + '"');
  	    }

  	    const md = compileLanguage(language);
  	    let result = '';
  	    /** @type {CompiledMode} */
  	    let top = continuation || md;
  	    /** @type Record<string,CompiledMode> */
  	    const continuations = {}; // keep continuations for sub-languages
  	    const emitter = new options.__emitter(options);
  	    processContinuations();
  	    let modeBuffer = '';
  	    let relevance = 0;
  	    let index = 0;
  	    let iterations = 0;
  	    let resumeScanAtSamePosition = false;

  	    try {
  	      if (!language.__emitTokens) {
  	        top.matcher.considerAll();

  	        for (;;) {
  	          iterations++;
  	          if (resumeScanAtSamePosition) {
  	            // only regexes not matched previously will now be
  	            // considered for a potential match
  	            resumeScanAtSamePosition = false;
  	          } else {
  	            top.matcher.considerAll();
  	          }
  	          top.matcher.lastIndex = index;

  	          const match = top.matcher.exec(codeToHighlight);
  	          // console.log("match", match[0], match.rule && match.rule.begin)

  	          if (!match) break;

  	          const beforeMatch = codeToHighlight.substring(index, match.index);
  	          const processedCount = processLexeme(beforeMatch, match);
  	          index = match.index + processedCount;
  	        }
  	        processLexeme(codeToHighlight.substring(index));
  	      } else {
  	        language.__emitTokens(codeToHighlight, emitter);
  	      }

  	      emitter.finalize();
  	      result = emitter.toHTML();

  	      return {
  	        language: languageName,
  	        value: result,
  	        relevance,
  	        illegal: false,
  	        _emitter: emitter,
  	        _top: top
  	      };
  	    } catch (err) {
  	      if (err.message && err.message.includes('Illegal')) {
  	        return {
  	          language: languageName,
  	          value: escape(codeToHighlight),
  	          illegal: true,
  	          relevance: 0,
  	          _illegalBy: {
  	            message: err.message,
  	            index,
  	            context: codeToHighlight.slice(index - 100, index + 100),
  	            mode: err.mode,
  	            resultSoFar: result
  	          },
  	          _emitter: emitter
  	        };
  	      } else if (SAFE_MODE) {
  	        return {
  	          language: languageName,
  	          value: escape(codeToHighlight),
  	          illegal: false,
  	          relevance: 0,
  	          errorRaised: err,
  	          _emitter: emitter,
  	          _top: top
  	        };
  	      } else {
  	        throw err;
  	      }
  	    }
  	  }

  	  /**
  	   * returns a valid highlight result, without actually doing any actual work,
  	   * auto highlight starts with this and it's possible for small snippets that
  	   * auto-detection may not find a better match
  	   * @param {string} code
  	   * @returns {HighlightResult}
  	   */
  	  function justTextHighlightResult(code) {
  	    const result = {
  	      value: escape(code),
  	      illegal: false,
  	      relevance: 0,
  	      _top: PLAINTEXT_LANGUAGE,
  	      _emitter: new options.__emitter(options)
  	    };
  	    result._emitter.addText(code);
  	    return result;
  	  }

  	  /**
  	  Highlighting with language detection. Accepts a string with the code to
  	  highlight. Returns an object with the following properties:

  	  - language (detected language)
  	  - relevance (int)
  	  - value (an HTML string with highlighting markup)
  	  - secondBest (object with the same structure for second-best heuristically
  	    detected language, may be absent)

  	    @param {string} code
  	    @param {Array<string>} [languageSubset]
  	    @returns {AutoHighlightResult}
  	  */
  	  function highlightAuto(code, languageSubset) {
  	    languageSubset = languageSubset || options.languages || Object.keys(languages);
  	    const plaintext = justTextHighlightResult(code);

  	    const results = languageSubset.filter(getLanguage).filter(autoDetection).map(name =>
  	      _highlight(name, code, false)
  	    );
  	    results.unshift(plaintext); // plaintext is always an option

  	    const sorted = results.sort((a, b) => {
  	      // sort base on relevance
  	      if (a.relevance !== b.relevance) return b.relevance - a.relevance;

  	      // always award the tie to the base language
  	      // ie if C++ and Arduino are tied, it's more likely to be C++
  	      if (a.language && b.language) {
  	        if (getLanguage(a.language).supersetOf === b.language) {
  	          return 1;
  	        } else if (getLanguage(b.language).supersetOf === a.language) {
  	          return -1;
  	        }
  	      }

  	      // otherwise say they are equal, which has the effect of sorting on
  	      // relevance while preserving the original ordering - which is how ties
  	      // have historically been settled, ie the language that comes first always
  	      // wins in the case of a tie
  	      return 0;
  	    });

  	    const [best, secondBest] = sorted;

  	    /** @type {AutoHighlightResult} */
  	    const result = best;
  	    result.secondBest = secondBest;

  	    return result;
  	  }

  	  /**
  	   * Builds new class name for block given the language name
  	   *
  	   * @param {HTMLElement} element
  	   * @param {string} [currentLang]
  	   * @param {string} [resultLang]
  	   */
  	  function updateClassName(element, currentLang, resultLang) {
  	    const language = (currentLang && aliases[currentLang]) || resultLang;

  	    element.classList.add("hljs");
  	    element.classList.add(`language-${language}`);
  	  }

  	  /**
  	   * Applies highlighting to a DOM node containing code.
  	   *
  	   * @param {HighlightedHTMLElement} element - the HTML element to highlight
  	  */
  	  function highlightElement(element) {
  	    /** @type HTMLElement */
  	    let node = null;
  	    const language = blockLanguage(element);

  	    if (shouldNotHighlight(language)) return;

  	    fire("before:highlightElement",
  	      { el: element, language });

  	    if (element.dataset.highlighted) {
  	      console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.", element);
  	      return;
  	    }

  	    // we should be all text, no child nodes (unescaped HTML) - this is possibly
  	    // an HTML injection attack - it's likely too late if this is already in
  	    // production (the code has likely already done its damage by the time
  	    // we're seeing it)... but we yell loudly about this so that hopefully it's
  	    // more likely to be caught in development before making it to production
  	    if (element.children.length > 0) {
  	      if (!options.ignoreUnescapedHTML) {
  	        console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk.");
  	        console.warn("https://github.com/highlightjs/highlight.js/wiki/security");
  	        console.warn("The element with unescaped HTML:");
  	        console.warn(element);
  	      }
  	      if (options.throwUnescapedHTML) {
  	        const err = new HTMLInjectionError(
  	          "One of your code blocks includes unescaped HTML.",
  	          element.innerHTML
  	        );
  	        throw err;
  	      }
  	    }

  	    node = element;
  	    const text = node.textContent;
  	    const result = language ? highlight(text, { language, ignoreIllegals: true }) : highlightAuto(text);

  	    element.innerHTML = result.value;
  	    element.dataset.highlighted = "yes";
  	    updateClassName(element, language, result.language);
  	    element.result = {
  	      language: result.language,
  	      // TODO: remove with version 11.0
  	      re: result.relevance,
  	      relevance: result.relevance
  	    };
  	    if (result.secondBest) {
  	      element.secondBest = {
  	        language: result.secondBest.language,
  	        relevance: result.secondBest.relevance
  	      };
  	    }

  	    fire("after:highlightElement", { el: element, result, text });
  	  }

  	  /**
  	   * Updates highlight.js global options with the passed options
  	   *
  	   * @param {Partial<HLJSOptions>} userOptions
  	   */
  	  function configure(userOptions) {
  	    options = inherit(options, userOptions);
  	  }

  	  // TODO: remove v12, deprecated
  	  const initHighlighting = () => {
  	    highlightAll();
  	    deprecated("10.6.0", "initHighlighting() deprecated.  Use highlightAll() now.");
  	  };

  	  // TODO: remove v12, deprecated
  	  function initHighlightingOnLoad() {
  	    highlightAll();
  	    deprecated("10.6.0", "initHighlightingOnLoad() deprecated.  Use highlightAll() now.");
  	  }

  	  let wantsHighlight = false;

  	  /**
  	   * auto-highlights all pre>code elements on the page
  	   */
  	  function highlightAll() {
  	    function boot() {
  	      // if a highlight was requested before DOM was loaded, do now
  	      highlightAll();
  	    }

  	    // if we are called too early in the loading process
  	    if (document.readyState === "loading") {
  	      // make sure the event listener is only added once
  	      if (!wantsHighlight) {
  	        window.addEventListener('DOMContentLoaded', boot, false);
  	      }
  	      wantsHighlight = true;
  	      return;
  	    }

  	    const blocks = document.querySelectorAll(options.cssSelector);
  	    blocks.forEach(highlightElement);
  	  }

  	  /**
  	   * Register a language grammar module
  	   *
  	   * @param {string} languageName
  	   * @param {LanguageFn} languageDefinition
  	   */
  	  function registerLanguage(languageName, languageDefinition) {
  	    let lang = null;
  	    try {
  	      lang = languageDefinition(hljs);
  	    } catch (error$1) {
  	      error("Language definition for '{}' could not be registered.".replace("{}", languageName));
  	      // hard or soft error
  	      if (!SAFE_MODE) { throw error$1; } else { error(error$1); }
  	      // languages that have serious errors are replaced with essentially a
  	      // "plaintext" stand-in so that the code blocks will still get normal
  	      // css classes applied to them - and one bad language won't break the
  	      // entire highlighter
  	      lang = PLAINTEXT_LANGUAGE;
  	    }
  	    // give it a temporary name if it doesn't have one in the meta-data
  	    if (!lang.name) lang.name = languageName;
  	    languages[languageName] = lang;
  	    lang.rawDefinition = languageDefinition.bind(null, hljs);

  	    if (lang.aliases) {
  	      registerAliases(lang.aliases, { languageName });
  	    }
  	  }

  	  /**
  	   * Remove a language grammar module
  	   *
  	   * @param {string} languageName
  	   */
  	  function unregisterLanguage(languageName) {
  	    delete languages[languageName];
  	    for (const alias of Object.keys(aliases)) {
  	      if (aliases[alias] === languageName) {
  	        delete aliases[alias];
  	      }
  	    }
  	  }

  	  /**
  	   * @returns {string[]} List of language internal names
  	   */
  	  function listLanguages() {
  	    return Object.keys(languages);
  	  }

  	  /**
  	   * @param {string} name - name of the language to retrieve
  	   * @returns {Language | undefined}
  	   */
  	  function getLanguage(name) {
  	    name = (name || '').toLowerCase();
  	    return languages[name] || languages[aliases[name]];
  	  }

  	  /**
  	   *
  	   * @param {string|string[]} aliasList - single alias or list of aliases
  	   * @param {{languageName: string}} opts
  	   */
  	  function registerAliases(aliasList, { languageName }) {
  	    if (typeof aliasList === 'string') {
  	      aliasList = [aliasList];
  	    }
  	    aliasList.forEach(alias => { aliases[alias.toLowerCase()] = languageName; });
  	  }

  	  /**
  	   * Determines if a given language has auto-detection enabled
  	   * @param {string} name - name of the language
  	   */
  	  function autoDetection(name) {
  	    const lang = getLanguage(name);
  	    return lang && !lang.disableAutodetect;
  	  }

  	  /**
  	   * Upgrades the old highlightBlock plugins to the new
  	   * highlightElement API
  	   * @param {HLJSPlugin} plugin
  	   */
  	  function upgradePluginAPI(plugin) {
  	    // TODO: remove with v12
  	    if (plugin["before:highlightBlock"] && !plugin["before:highlightElement"]) {
  	      plugin["before:highlightElement"] = (data) => {
  	        plugin["before:highlightBlock"](
  	          Object.assign({ block: data.el }, data)
  	        );
  	      };
  	    }
  	    if (plugin["after:highlightBlock"] && !plugin["after:highlightElement"]) {
  	      plugin["after:highlightElement"] = (data) => {
  	        plugin["after:highlightBlock"](
  	          Object.assign({ block: data.el }, data)
  	        );
  	      };
  	    }
  	  }

  	  /**
  	   * @param {HLJSPlugin} plugin
  	   */
  	  function addPlugin(plugin) {
  	    upgradePluginAPI(plugin);
  	    plugins.push(plugin);
  	  }

  	  /**
  	   * @param {HLJSPlugin} plugin
  	   */
  	  function removePlugin(plugin) {
  	    const index = plugins.indexOf(plugin);
  	    if (index !== -1) {
  	      plugins.splice(index, 1);
  	    }
  	  }

  	  /**
  	   *
  	   * @param {PluginEvent} event
  	   * @param {any} args
  	   */
  	  function fire(event, args) {
  	    const cb = event;
  	    plugins.forEach(function(plugin) {
  	      if (plugin[cb]) {
  	        plugin[cb](args);
  	      }
  	    });
  	  }

  	  /**
  	   * DEPRECATED
  	   * @param {HighlightedHTMLElement} el
  	   */
  	  function deprecateHighlightBlock(el) {
  	    deprecated("10.7.0", "highlightBlock will be removed entirely in v12.0");
  	    deprecated("10.7.0", "Please use highlightElement now.");

  	    return highlightElement(el);
  	  }

  	  /* Interface definition */
  	  Object.assign(hljs, {
  	    highlight,
  	    highlightAuto,
  	    highlightAll,
  	    highlightElement,
  	    // TODO: Remove with v12 API
  	    highlightBlock: deprecateHighlightBlock,
  	    configure,
  	    initHighlighting,
  	    initHighlightingOnLoad,
  	    registerLanguage,
  	    unregisterLanguage,
  	    listLanguages,
  	    getLanguage,
  	    registerAliases,
  	    autoDetection,
  	    inherit,
  	    addPlugin,
  	    removePlugin
  	  });

  	  hljs.debugMode = function() { SAFE_MODE = false; };
  	  hljs.safeMode = function() { SAFE_MODE = true; };
  	  hljs.versionString = version;

  	  hljs.regex = {
  	    concat: concat,
  	    lookahead: lookahead,
  	    either: either,
  	    optional: optional,
  	    anyNumberOfTimes: anyNumberOfTimes
  	  };

  	  for (const key in MODES) {
  	    // @ts-ignore
  	    if (typeof MODES[key] === "object") {
  	      // @ts-ignore
  	      deepFreeze(MODES[key]);
  	    }
  	  }

  	  // merge all the modes/regexes into our main object
  	  Object.assign(hljs, MODES);

  	  return hljs;
  	};

  	// Other names for the variable may break build script
  	const highlight = HLJS({});

  	// returns a new instance of the highlighter to be used for extensions
  	// check https://github.com/wooorm/lowlight/issues/47
  	highlight.newInstance = () => HLJS({});

  	core = highlight;
  	highlight.HighlightJS = highlight;
  	highlight.default = highlight;
  	return core;
  }

  var coreExports = /*@__PURE__*/ requireCore();
  var HighlightJS = /*@__PURE__*/getDefaultExportFromCjs(coreExports);

  const IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*';
  const KEYWORDS = [
    "as", // for exports
    "in",
    "of",
    "if",
    "for",
    "while",
    "finally",
    "var",
    "new",
    "function",
    "do",
    "return",
    "void",
    "else",
    "break",
    "catch",
    "instanceof",
    "with",
    "throw",
    "case",
    "default",
    "try",
    "switch",
    "continue",
    "typeof",
    "delete",
    "let",
    "yield",
    "const",
    "class",
    // JS handles these with a special rule
    // "get",
    // "set",
    "debugger",
    "async",
    "await",
    "static",
    "import",
    "from",
    "export",
    "extends",
    // It's reached stage 3, which is "recommended for implementation":
    "using"
  ];
  const LITERALS = [
    "true",
    "false",
    "null",
    "undefined",
    "NaN",
    "Infinity"
  ];

  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects
  const TYPES = [
    // Fundamental objects
    "Object",
    "Function",
    "Boolean",
    "Symbol",
    // numbers and dates
    "Math",
    "Date",
    "Number",
    "BigInt",
    // text
    "String",
    "RegExp",
    // Indexed collections
    "Array",
    "Float32Array",
    "Float64Array",
    "Int8Array",
    "Uint8Array",
    "Uint8ClampedArray",
    "Int16Array",
    "Int32Array",
    "Uint16Array",
    "Uint32Array",
    "BigInt64Array",
    "BigUint64Array",
    // Keyed collections
    "Set",
    "Map",
    "WeakSet",
    "WeakMap",
    // Structured data
    "ArrayBuffer",
    "SharedArrayBuffer",
    "Atomics",
    "DataView",
    "JSON",
    // Control abstraction objects
    "Promise",
    "Generator",
    "GeneratorFunction",
    "AsyncFunction",
    // Reflection
    "Reflect",
    "Proxy",
    // Internationalization
    "Intl",
    // WebAssembly
    "WebAssembly"
  ];

  const ERROR_TYPES = [
    "Error",
    "EvalError",
    "InternalError",
    "RangeError",
    "ReferenceError",
    "SyntaxError",
    "TypeError",
    "URIError"
  ];

  const BUILT_IN_GLOBALS = [
    "setInterval",
    "setTimeout",
    "clearInterval",
    "clearTimeout",

    "require",
    "exports",

    "eval",
    "isFinite",
    "isNaN",
    "parseFloat",
    "parseInt",
    "decodeURI",
    "decodeURIComponent",
    "encodeURI",
    "encodeURIComponent",
    "escape",
    "unescape"
  ];

  const BUILT_IN_VARIABLES = [
    "arguments",
    "this",
    "super",
    "console",
    "window",
    "document",
    "localStorage",
    "sessionStorage",
    "module",
    "global" // Node.js
  ];

  const BUILT_INS = [].concat(
    BUILT_IN_GLOBALS,
    TYPES,
    ERROR_TYPES
  );

  /*
  Language: JavaScript
  Description: JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions.
  Category: common, scripting, web
  Website: https://developer.mozilla.org/en-US/docs/Web/JavaScript
  */


  /** @type LanguageFn */
  function javascript(hljs) {
    const regex = hljs.regex;
    /**
     * Takes a string like "<Booger" and checks to see
     * if we can find a matching "</Booger" later in the
     * content.
     * @param {RegExpMatchArray} match
     * @param {{after:number}} param1
     */
    const hasClosingTag = (match, { after }) => {
      const tag = "</" + match[0].slice(1);
      const pos = match.input.indexOf(tag, after);
      return pos !== -1;
    };

    const IDENT_RE$1 = IDENT_RE;
    const FRAGMENT = {
      begin: '<>',
      end: '</>'
    };
    // to avoid some special cases inside isTrulyOpeningTag
    const XML_SELF_CLOSING = /<[A-Za-z0-9\\._:-]+\s*\/>/;
    const XML_TAG = {
      begin: /<[A-Za-z0-9\\._:-]+/,
      end: /\/[A-Za-z0-9\\._:-]+>|\/>/,
      /**
       * @param {RegExpMatchArray} match
       * @param {CallbackResponse} response
       */
      isTrulyOpeningTag: (match, response) => {
        const afterMatchIndex = match[0].length + match.index;
        const nextChar = match.input[afterMatchIndex];
        if (
          // HTML should not include another raw `<` inside a tag
          // nested type?
          // `<Array<Array<number>>`, etc.
          nextChar === "<" ||
          // the , gives away that this is not HTML
          // `<T, A extends keyof T, V>`
          nextChar === ","
          ) {
          response.ignoreMatch();
          return;
        }

        // `<something>`
        // Quite possibly a tag, lets look for a matching closing tag...
        if (nextChar === ">") {
          // if we cannot find a matching closing tag, then we
          // will ignore it
          if (!hasClosingTag(match, { after: afterMatchIndex })) {
            response.ignoreMatch();
          }
        }

        // `<blah />` (self-closing)
        // handled by simpleSelfClosing rule

        let m;
        const afterMatch = match.input.substring(afterMatchIndex);

        // some more template typing stuff
        //  <T = any>(key?: string) => Modify<
        if ((m = afterMatch.match(/^\s*=/))) {
          response.ignoreMatch();
          return;
        }

        // `<From extends string>`
        // technically this could be HTML, but it smells like a type
        // NOTE: This is ugh, but added specifically for https://github.com/highlightjs/highlight.js/issues/3276
        if ((m = afterMatch.match(/^\s+extends\s+/))) {
          if (m.index === 0) {
            response.ignoreMatch();
            // eslint-disable-next-line no-useless-return
            return;
          }
        }
      }
    };
    const KEYWORDS$1 = {
      $pattern: IDENT_RE,
      keyword: KEYWORDS,
      literal: LITERALS,
      built_in: BUILT_INS,
      "variable.language": BUILT_IN_VARIABLES
    };

    // https://tc39.es/ecma262/#sec-literals-numeric-literals
    const decimalDigits = '[0-9](_?[0-9])*';
    const frac = `\\.(${decimalDigits})`;
    // DecimalIntegerLiteral, including Annex B NonOctalDecimalIntegerLiteral
    // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
    const decimalInteger = `0|[1-9](_?[0-9])*|0[0-7]*[89][0-9]*`;
    const NUMBER = {
      className: 'number',
      variants: [
        // DecimalLiteral
        { begin: `(\\b(${decimalInteger})((${frac})|\\.)?|(${frac}))` +
          `[eE][+-]?(${decimalDigits})\\b` },
        { begin: `\\b(${decimalInteger})\\b((${frac})\\b|\\.)?|(${frac})\\b` },

        // DecimalBigIntegerLiteral
        { begin: `\\b(0|[1-9](_?[0-9])*)n\\b` },

        // NonDecimalIntegerLiteral
        { begin: "\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*n?\\b" },
        { begin: "\\b0[bB][0-1](_?[0-1])*n?\\b" },
        { begin: "\\b0[oO][0-7](_?[0-7])*n?\\b" },

        // LegacyOctalIntegerLiteral (does not include underscore separators)
        // https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals
        { begin: "\\b0[0-7]+n?\\b" },
      ],
      relevance: 0
    };

    const SUBST = {
      className: 'subst',
      begin: '\\$\\{',
      end: '\\}',
      keywords: KEYWORDS$1,
      contains: [] // defined later
    };
    const HTML_TEMPLATE = {
      begin: '\.?html`',
      end: '',
      starts: {
        end: '`',
        returnEnd: false,
        contains: [
          hljs.BACKSLASH_ESCAPE,
          SUBST
        ],
        subLanguage: 'xml'
      }
    };
    const CSS_TEMPLATE = {
      begin: '\.?css`',
      end: '',
      starts: {
        end: '`',
        returnEnd: false,
        contains: [
          hljs.BACKSLASH_ESCAPE,
          SUBST
        ],
        subLanguage: 'css'
      }
    };
    const GRAPHQL_TEMPLATE = {
      begin: '\.?gql`',
      end: '',
      starts: {
        end: '`',
        returnEnd: false,
        contains: [
          hljs.BACKSLASH_ESCAPE,
          SUBST
        ],
        subLanguage: 'graphql'
      }
    };
    const TEMPLATE_STRING = {
      className: 'string',
      begin: '`',
      end: '`',
      contains: [
        hljs.BACKSLASH_ESCAPE,
        SUBST
      ]
    };
    const JSDOC_COMMENT = hljs.COMMENT(
      /\/\*\*(?!\/)/,
      '\\*/',
      {
        relevance: 0,
        contains: [
          {
            begin: '(?=@[A-Za-z]+)',
            relevance: 0,
            contains: [
              {
                className: 'doctag',
                begin: '@[A-Za-z]+'
              },
              {
                className: 'type',
                begin: '\\{',
                end: '\\}',
                excludeEnd: true,
                excludeBegin: true,
                relevance: 0
              },
              {
                className: 'variable',
                begin: IDENT_RE$1 + '(?=\\s*(-)|$)',
                endsParent: true,
                relevance: 0
              },
              // eat spaces (not newlines) so we can find
              // types or variables
              {
                begin: /(?=[^\n])\s/,
                relevance: 0
              }
            ]
          }
        ]
      }
    );
    const COMMENT = {
      className: "comment",
      variants: [
        JSDOC_COMMENT,
        hljs.C_BLOCK_COMMENT_MODE,
        hljs.C_LINE_COMMENT_MODE
      ]
    };
    const SUBST_INTERNALS = [
      hljs.APOS_STRING_MODE,
      hljs.QUOTE_STRING_MODE,
      HTML_TEMPLATE,
      CSS_TEMPLATE,
      GRAPHQL_TEMPLATE,
      TEMPLATE_STRING,
      // Skip numbers when they are part of a variable name
      { match: /\$\d+/ },
      NUMBER,
      // This is intentional:
      // See https://github.com/highlightjs/highlight.js/issues/3288
      // hljs.REGEXP_MODE
    ];
    SUBST.contains = SUBST_INTERNALS
      .concat({
        // we need to pair up {} inside our subst to prevent
        // it from ending too early by matching another }
        begin: /\{/,
        end: /\}/,
        keywords: KEYWORDS$1,
        contains: [
          "self"
        ].concat(SUBST_INTERNALS)
      });
    const SUBST_AND_COMMENTS = [].concat(COMMENT, SUBST.contains);
    const PARAMS_CONTAINS = SUBST_AND_COMMENTS.concat([
      // eat recursive parens in sub expressions
      {
        begin: /(\s*)\(/,
        end: /\)/,
        keywords: KEYWORDS$1,
        contains: ["self"].concat(SUBST_AND_COMMENTS)
      }
    ]);
    const PARAMS = {
      className: 'params',
      // convert this to negative lookbehind in v12
      begin: /(\s*)\(/, // to match the parms with
      end: /\)/,
      excludeBegin: true,
      excludeEnd: true,
      keywords: KEYWORDS$1,
      contains: PARAMS_CONTAINS
    };

    // ES6 classes
    const CLASS_OR_EXTENDS = {
      variants: [
        // class Car extends vehicle
        {
          match: [
            /class/,
            /\s+/,
            IDENT_RE$1,
            /\s+/,
            /extends/,
            /\s+/,
            regex.concat(IDENT_RE$1, "(", regex.concat(/\./, IDENT_RE$1), ")*")
          ],
          scope: {
            1: "keyword",
            3: "title.class",
            5: "keyword",
            7: "title.class.inherited"
          }
        },
        // class Car
        {
          match: [
            /class/,
            /\s+/,
            IDENT_RE$1
          ],
          scope: {
            1: "keyword",
            3: "title.class"
          }
        },

      ]
    };

    const CLASS_REFERENCE = {
      relevance: 0,
      match:
      regex.either(
        // Hard coded exceptions
        /\bJSON/,
        // Float32Array, OutT
        /\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,
        // CSSFactory, CSSFactoryT
        /\b[A-Z]{2,}([A-Z][a-z]+|\d)+([A-Z][a-z]*)*/,
        // FPs, FPsT
        /\b[A-Z]{2,}[a-z]+([A-Z][a-z]+|\d)*([A-Z][a-z]*)*/,
        // P
        // single letters are not highlighted
        // BLAH
        // this will be flagged as a UPPER_CASE_CONSTANT instead
      ),
      className: "title.class",
      keywords: {
        _: [
          // se we still get relevance credit for JS library classes
          ...TYPES,
          ...ERROR_TYPES
        ]
      }
    };

    const USE_STRICT = {
      label: "use_strict",
      className: 'meta',
      relevance: 10,
      begin: /^\s*['"]use (strict|asm)['"]/
    };

    const FUNCTION_DEFINITION = {
      variants: [
        {
          match: [
            /function/,
            /\s+/,
            IDENT_RE$1,
            /(?=\s*\()/
          ]
        },
        // anonymous function
        {
          match: [
            /function/,
            /\s*(?=\()/
          ]
        }
      ],
      className: {
        1: "keyword",
        3: "title.function"
      },
      label: "func.def",
      contains: [ PARAMS ],
      illegal: /%/
    };

    const UPPER_CASE_CONSTANT = {
      relevance: 0,
      match: /\b[A-Z][A-Z_0-9]+\b/,
      className: "variable.constant"
    };

    function noneOf(list) {
      return regex.concat("(?!", list.join("|"), ")");
    }

    const FUNCTION_CALL = {
      match: regex.concat(
        /\b/,
        noneOf([
          ...BUILT_IN_GLOBALS,
          "super",
          "import"
        ].map(x => `${x}\\s*\\(`)),
        IDENT_RE$1, regex.lookahead(/\s*\(/)),
      className: "title.function",
      relevance: 0
    };

    const PROPERTY_ACCESS = {
      begin: regex.concat(/\./, regex.lookahead(
        regex.concat(IDENT_RE$1, /(?![0-9A-Za-z$_(])/)
      )),
      end: IDENT_RE$1,
      excludeBegin: true,
      keywords: "prototype",
      className: "property",
      relevance: 0
    };

    const GETTER_OR_SETTER = {
      match: [
        /get|set/,
        /\s+/,
        IDENT_RE$1,
        /(?=\()/
      ],
      className: {
        1: "keyword",
        3: "title.function"
      },
      contains: [
        { // eat to avoid empty params
          begin: /\(\)/
        },
        PARAMS
      ]
    };

    const FUNC_LEAD_IN_RE = '(\\(' +
      '[^()]*(\\(' +
      '[^()]*(\\(' +
      '[^()]*' +
      '\\)[^()]*)*' +
      '\\)[^()]*)*' +
      '\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>';

    const FUNCTION_VARIABLE = {
      match: [
        /const|var|let/, /\s+/,
        IDENT_RE$1, /\s*/,
        /=\s*/,
        /(async\s*)?/, // async is optional
        regex.lookahead(FUNC_LEAD_IN_RE)
      ],
      keywords: "async",
      className: {
        1: "keyword",
        3: "title.function"
      },
      contains: [
        PARAMS
      ]
    };

    return {
      name: 'JavaScript',
      aliases: ['js', 'jsx', 'mjs', 'cjs'],
      keywords: KEYWORDS$1,
      // this will be extended by TypeScript
      exports: { PARAMS_CONTAINS, CLASS_REFERENCE },
      illegal: /#(?![$_A-z])/,
      contains: [
        hljs.SHEBANG({
          label: "shebang",
          binary: "node",
          relevance: 5
        }),
        USE_STRICT,
        hljs.APOS_STRING_MODE,
        hljs.QUOTE_STRING_MODE,
        HTML_TEMPLATE,
        CSS_TEMPLATE,
        GRAPHQL_TEMPLATE,
        TEMPLATE_STRING,
        COMMENT,
        // Skip numbers when they are part of a variable name
        { match: /\$\d+/ },
        NUMBER,
        CLASS_REFERENCE,
        {
          scope: 'attr',
          match: IDENT_RE$1 + regex.lookahead(':'),
          relevance: 0
        },
        FUNCTION_VARIABLE,
        { // "value" container
          begin: '(' + hljs.RE_STARTERS_RE + '|\\b(case|return|throw)\\b)\\s*',
          keywords: 'return throw case',
          relevance: 0,
          contains: [
            COMMENT,
            hljs.REGEXP_MODE,
            {
              className: 'function',
              // we have to count the parens to make sure we actually have the
              // correct bounding ( ) before the =>.  There could be any number of
              // sub-expressions inside also surrounded by parens.
              begin: FUNC_LEAD_IN_RE,
              returnBegin: true,
              end: '\\s*=>',
              contains: [
                {
                  className: 'params',
                  variants: [
                    {
                      begin: hljs.UNDERSCORE_IDENT_RE,
                      relevance: 0
                    },
                    {
                      className: null,
                      begin: /\(\s*\)/,
                      skip: true
                    },
                    {
                      begin: /(\s*)\(/,
                      end: /\)/,
                      excludeBegin: true,
                      excludeEnd: true,
                      keywords: KEYWORDS$1,
                      contains: PARAMS_CONTAINS
                    }
                  ]
                }
              ]
            },
            { // could be a comma delimited list of params to a function call
              begin: /,/,
              relevance: 0
            },
            {
              match: /\s+/,
              relevance: 0
            },
            { // JSX
              variants: [
                { begin: FRAGMENT.begin, end: FRAGMENT.end },
                { match: XML_SELF_CLOSING },
                {
                  begin: XML_TAG.begin,
                  // we carefully check the opening tag to see if it truly
                  // is a tag and not a false positive
                  'on:begin': XML_TAG.isTrulyOpeningTag,
                  end: XML_TAG.end
                }
              ],
              subLanguage: 'xml',
              contains: [
                {
                  begin: XML_TAG.begin,
                  end: XML_TAG.end,
                  skip: true,
                  contains: ['self']
                }
              ]
            }
          ],
        },
        FUNCTION_DEFINITION,
        {
          // prevent this from getting swallowed up by function
          // since they appear "function like"
          beginKeywords: "while if switch catch for"
        },
        {
          // we have to count the parens to make sure we actually have the correct
          // bounding ( ).  There could be any number of sub-expressions inside
          // also surrounded by parens.
          begin: '\\b(?!function)' + hljs.UNDERSCORE_IDENT_RE +
            '\\(' + // first parens
            '[^()]*(\\(' +
              '[^()]*(\\(' +
                '[^()]*' +
              '\\)[^()]*)*' +
            '\\)[^()]*)*' +
            '\\)\\s*\\{', // end parens
          returnBegin:true,
          label: "func.def",
          contains: [
            PARAMS,
            hljs.inherit(hljs.TITLE_MODE, { begin: IDENT_RE$1, className: "title.function" })
          ]
        },
        // catch ... so it won't trigger the property rule below
        {
          match: /\.\.\./,
          relevance: 0
        },
        PROPERTY_ACCESS,
        // hack: prevents detection of keywords in some circumstances
        // .keyword()
        // $keyword = x
        {
          match: '\\$' + IDENT_RE$1,
          relevance: 0
        },
        {
          match: [ /\bconstructor(?=\s*\()/ ],
          className: { 1: "title.function" },
          contains: [ PARAMS ]
        },
        FUNCTION_CALL,
        UPPER_CASE_CONSTANT,
        CLASS_OR_EXTENDS,
        GETTER_OR_SETTER,
        {
          match: /\$[(.]/ // relevance booster for a pattern common to JS libs: `$(something)` and `$.something`
        }
      ]
    };
  }

  HighlightJS.registerLanguage('javascript', javascript);

  (function() {

      // --- 設定値 ---
      const MAX_IMAGE_DIMENSION = 1280; // 画像の最大辺の長さ (これより大きい場合リサイズ)
      const JPEG_QUALITY = 0.8;         // JPEG圧縮の品質 (0.0 - 1.0)

      // --- 国際化 (i18n) のための文字列定義 ---
      const I18N = {
          en: {
              editor: 'Editor', split: 'Split', preview: 'Preview', toggleToolbar: 'Toggle Toolbar',
              exportPDF: 'Print / Export as PDF', paragraph: 'Paragraph', heading1: 'Heading 1',
              heading2: 'Heading 2', heading3: 'Heading 3', heading4: 'Heading 4', bold: 'Bold',
              italic: 'Italic', strikethrough: 'Strikethrough', inlineCode: 'Inline Code', quote: 'Quote',
              list: 'Bulleted List', numberedList: 'Numbered List', checklist: 'Checklist',
              codeBlock: 'Code Block', link: 'Link', insertTable: 'Insert Table', horizontalRule: 'Horizontal Rule',
              image: 'Image',
              linkPrompt: 'Enter the link URL:', boldPlaceholder: 'bold text', italicPlaceholder: 'italic text',
              strikethroughPlaceholder: 'strikethrough', codePlaceholder: 'code', quotePlaceholder: 'quote',
              listItemPlaceholder: 'item', taskPlaceholder: 'task', linkTextPlaceholder: 'link text',
              copy: 'Copy', copied: 'Copied!', copyError: 'Error', copyAriaLabel: 'Copy code to clipboard',
              previewErrorTitle: 'An error occurred while updating the preview:', printPDF: 'PDF',
              pastedImageAltText: 'Pasted Image at',
              insertImage: 'Insert Image', fromURL: 'From URL', uploadFile: 'Upload File',
              imageURL: 'Image URL', altText: 'Alt Text (optional)', chooseFile: 'Choose a file...',
              insert: 'Insert', close: 'Close', processing: 'Processing...',
              errorImageProcessing: 'Failed to process image.',
          },
          ja: {
              editor: 'エディタ', split: '分割', preview: 'プレビュー', toggleToolbar: 'ツールバー表示切替',
              exportPDF: 'PDFとして印刷/エクスポート', paragraph: '段落', heading1: '見出し 1',
              heading2: '見出し 2', heading3: '見出し 3', heading4: '見出し 4', bold: '太字',
              italic: '斜体', strikethrough: '打ち消し線', inlineCode: 'インラインコード', quote: '引用',
              list: 'リスト', numberedList: '番号付きリスト', checklist: 'チェックリスト',
              codeBlock: 'コードブロック', link: 'リンク', insertTable: 'テーブル挿入', horizontalRule: '水平線',
              image: '画像',
              linkPrompt: 'リンク先のURLを入力してください:', boldPlaceholder: '太字', italicPlaceholder: '斜体',
              strikethroughPlaceholder: '打ち消し', codePlaceholder: 'code', quotePlaceholder: '引用文',
              listItemPlaceholder: '項目', taskPlaceholder: 'タスク', linkTextPlaceholder: 'リンクテキスト',
              copy: 'Copy', copied: 'Copied!', copyError: 'Error', copyAriaLabel: 'クリップボードにコードをコピー',
              previewErrorTitle: 'プレビューの更新中にエラーが発生しました:', printPDF: 'PDF',
              pastedImageAltText: '貼り付けられた画像',
              insertImage: '画像を挿入', fromURL: 'URLから', uploadFile: 'ファイルをアップロード',
              imageURL: '画像のURL', altText: '代替テキスト(任意)', chooseFile: 'ファイルを選択...',
              insert: '挿入', close: '閉じる', processing: '処理中...',
              errorImageProcessing: '画像の処理に失敗しました。',
          }
      };
      const lang = navigator.language.startsWith('ja') ? 'ja' : 'en';
      const T = I18N[lang] || I18N.en;

      const STORAGE_KEY_MODE = 'snMarkdownEditorMode';
      const STORAGE_KEY_TOOLBAR_VISIBLE = 'snMarkdownToolbarVisible';

      // --- スタイル定義 ---
      GM_addStyle(`
        .markdown-editor-container { display: flex; flex-direction: column; height: 100%; overflow: hidden; border: 1px solid var(--sn-stylekit-border-color, #e0e0e0); border-radius: 4px; }
        .mode-toggle-bar { flex-shrink: 0; padding: 4px 10px; background-color: var(--sn-stylekit-editor-background-color, #f9f9f9); border-bottom: 1px solid var(--sn-stylekit-border-color, #e0e0e0); display: flex; align-items: center; gap: 5px; }
        .mode-toggle-button { padding: 5px 12px; border: 1px solid var(--sn-stylekit-border-color, #ccc); border-radius: 6px; cursor: pointer; background-color: var(--sn-stylekit-background-color, #fff); color: var(--sn-stylekit-foreground-color, #333); font-size: 13px; }
        .mode-toggle-button.active { background-color: var(--sn-stylekit-primary-color, #346df1); color: var(--sn-stylekit-primary-contrast-color, #fff); border-color: var(--sn-stylekit-primary-color, #346df1); }
        .toolbar-toggle-button { margin-left: auto; padding: 5px 8px; font-size: 13px; display: flex; align-items: center; justify-content: center; width: 30px; height: 30px; }
        .toolbar-toggle-button svg { width: 16px; height: 16px; fill: currentColor; }
        .toolbar-toggle-button.active { background-color: var(--sn-stylekit-secondary-background-color, #f0f0f0); }
        .pdf-export-button { padding: 4px 10px; font-size: 12px; }
        .markdown-toolbar { flex-shrink: 0; display: flex; flex-wrap: wrap; align-items: center; padding: 8px 10px; gap: 8px; background-color: var(--sn-stylekit-editor-background-color, #f9f9f9); border-bottom: 1px solid var(--sn-stylekit-border-color, #e0e0e0); transition: display 0.2s; }
        .toolbar-button, .toolbar-select { padding: 4px 8px; border: 1px solid transparent; border-radius: 4px; cursor: pointer; background-color: var(--sn-stylekit-background-color, #fff); color: var(--sn-stylekit-foreground-color, #555); font-size: 14px; transition: all 0.2s; }
        .toolbar-button:hover, .toolbar-select:hover { background-color: var(--sn-stylekit-secondary-background-color, #f0f0f0); border-color: var(--sn-stylekit-border-color, #ccc); }
        .toolbar-button { font-weight: bold; }
        .toolbar-button.icon-button { font-weight: normal; padding: 5px; width: 30px; height: 30px; display: inline-flex; justify-content: center; align-items: center; }
        .toolbar-button.icon-button svg { width: 18px; height: 18px; fill: currentColor; }
        .toolbar-select { font-weight: bold; -webkit-appearance: none; -moz-appearance: none; appearance: none; padding-right: 20px; background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20fill%3D%22%23555%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%208l5%205%205-5z%22%2F%3E%3C%2Fsvg%3E'); background-repeat: no-repeat; background-position: right 0px center; }
        .markdown-editor-container.toolbar-hidden .markdown-toolbar { display: none; }
        .editor-preview-wrapper { display: flex; flex-grow: 1; overflow: hidden; background-color: var(--sn-stylekit-editor-background-color, #fff); }
        .custom-markdown-textarea, .markdown-preview { height: 100%; overflow-y: auto; flex-grow: 1; flex-shrink: 1; }
        .custom-markdown-textarea { border: none !important; outline: none !important; resize: none !important; box-shadow: none !important; padding: 16px !important; margin: 0 !important; width: 100% !important; background-color: transparent !important; color: var(--sn-stylekit-foreground-color, #333) !important; font-family: var(--sn-stylekit-font-editor, sans-serif) !important; line-height: var(--sn-stylekit-line-height-editor, 1.7) !important; }
        .markdown-preview { padding: 16px; line-height: 1.7; font-size: 1.05rem; color: var(--sn-stylekit-foreground-color, #333); }
        .markdown-editor-container.mode-editor .markdown-preview { display: none; }
        .markdown-editor-container.mode-preview .markdown-toolbar, .markdown-editor-container.mode-preview .custom-markdown-textarea { display: none; }
        .markdown-editor-container.mode-preview .markdown-preview { display: block; }
        .markdown-editor-container.mode-split .custom-markdown-textarea, .markdown-editor-container.mode-split .markdown-preview { display: block !important; flex-basis: 50%; width: 50%; }
        .markdown-editor-container.mode-split .markdown-preview { border-left: 1px solid var(--sn-stylekit-border-color, #e0e0e0); }
        .markdown-preview h1, .markdown-preview h2, .markdown-preview h3, .markdown-preview h4, .markdown-preview h5, .markdown-preview h6 { margin-top: 24px; margin-bottom: 16px; font-weight: 600; line-height: 1.25; border-bottom: 1px solid var(--sn-stylekit-border-color, #eee); padding-bottom: .3em; } .markdown-preview h1 { font-size: 2em; } .markdown-preview h2 { font-size: 1.5em; } .markdown-preview h3 { font-size: 1.25em; }
        .markdown-preview p { margin-bottom: 16px; } .markdown-preview ul, .markdown-preview ol { padding-left: 2em; margin-bottom: 16px; } .markdown-preview blockquote { padding: 0 1em; color: var(--sn-stylekit-secondary-foreground-color, #6a737d); border-left: .25em solid var(--sn-stylekit-border-color, #dfe2e5); margin: 0 0 16px 0; } .markdown-preview code { padding: .2em .4em; margin: 0; font-size: 85%; background-color: var(--sn-stylekit-secondary-background-color, #f0f0f0); border-radius: 3px; font-family: var(--sn-stylekit-font-code, monospace); } .markdown-preview pre { position: relative; padding: 16px; overflow: auto; font-size: 85%; line-height: 1.45; background-color: var(--sn-stylekit-secondary-background-color, #f0f0f0); border-radius: 6px; word-wrap: normal; margin-bottom: 16px; } .markdown-preview pre code { background-color: transparent; padding: 0; margin: 0; } .markdown-preview img { max-width: 100%; height: auto; border-radius: 6px; } .markdown-preview table { border-collapse: collapse; width: 100%; margin-bottom: 16px; display: block; overflow: auto; } .markdown-preview th, .markdown-preview td { border: 1px solid var(--sn-stylekit-border-color, #dfe2e5); padding: 6px 13px; } .markdown-preview tr:nth-child(2n) { background-color: var(--sn-stylekit-secondary-background-color, #f6f8fa); } .markdown-preview hr { height: .25em; padding: 0; margin: 24px 0; background-color: var(--sn-stylekit-border-color, #dfe2e5); border: 0; } .markdown-preview .task-list-item { list-style-type: none; } .markdown-preview .task-list-item-checkbox { margin: 0 .2em .25em -1.6em; vertical-align: middle; }
        .copy-code-button { position: absolute; top: 10px; right: 10px; padding: 5px 8px; font-size: 12px; border: 1px solid var(--sn-stylekit-border-color, #ccc); border-radius: 4px; background-color: var(--sn-stylekit-background-color, #fff); color: var(--sn-stylekit-secondary-foreground-color, #555); cursor: pointer; opacity: 0; transition: opacity 0.2s, background-color 0.2s, color 0.2s; z-index: 1; } .markdown-preview pre:hover .copy-code-button { opacity: 1; } .copy-code-button:hover { background-color: var(--sn-stylekit-secondary-background-color, #f0f0f0); } .copy-code-button.copied { background-color: var(--sn-stylekit-primary-color, #346df1); color: var(--sn-stylekit-primary-contrast-color, #fff); border-color: var(--sn-stylekit-primary-color, #346df1); }
        .markdown-preview pre code.hljs { display: block; overflow-x: auto; padding: 0; color: var(--sn-stylekit-foreground-color, #333); background: transparent; } .hljs-comment, .hljs-quote { color: var(--sn-stylekit-secondary-foreground-color, #6a737d); font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-subst, .hljs-deletion, .hljs-meta, .hljs-selector-class { color: #d73a49; } .hljs-number, .hljs-literal, .hljs-variable, .hljs-template-variable, .hljs-tag .hljs-attr { color: var(--sn-stylekit-primary-color, #005cc5); } .hljs-string, .hljs-doctag { color: #032f62; } .hljs-title, .hljs-section, .hljs-selector-id, .hljs-type, .hljs-symbol, .hljs-bullet, .hljs-link { color: #6f42c1; } .hljs-addition { color: #22863a; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; }
        @media (prefers-color-scheme: dark) { .markdown-preview pre code.hljs .hljs-keyword, .markdown-preview pre code.hljs .hljs-selector-tag, .markdown-preview pre code.hljs .hljs-subst, .markdown-preview pre code.hljs .hljs-deletion, .markdown-preview pre code.hljs .hljs-meta, .markdown-preview pre code.hljs .hljs-selector-class { color: #ff7b72; } .markdown-preview pre code.hljs .hljs-string, .markdown-preview pre code.hljs .hljs-doctag { color: #a5d6ff; } .markdown-preview pre code.hljs .hljs-title, .markdown-preview pre code.hljs .hljs-section, .markdown-preview pre code.hljs .hljs-selector-id, .markdown-preview pre code.hljs .hljs-type, .markdown-preview pre code.hljs .hljs-symbol, .markdown-preview pre code.hljs .hljs-bullet, .markdown-preview pre code.hljs .hljs-link { color: #d2a8ff; } .markdown-preview pre code.hljs .hljs-addition { color: #7ee787; } }
        @media print { body > *:not(.print-container) { display: none !important; } .print-container, .print-container > * { display: block !important; width: 100% !important; height: auto !important; overflow: visible !important; } html, body { margin: 0 !important; padding: 0 !important; background: #fff !important; } .markdown-preview { padding: 2cm !important; border: none !important; box-shadow: none !important; color: #000 !important; background-color: #fff !important; font-size: 12pt !important; line-height: 1.5 !important; } .markdown-preview h1, .markdown-preview h2, .markdown-preview h3, .markdown-preview h4, .markdown-preview h5, .markdown-preview h6 { color: #000 !important; border-bottom-color: #ccc !important; } .markdown-preview pre, .markdown-preview code { background-color: #f0f0f0 !important; color: #000 !important; border: 1px solid #ccc !important; } .markdown-preview pre code.hljs { color: #000 !important; } .markdown-preview blockquote { color: #333 !important; border-left-color: #ccc !important; } .markdown-preview tr:nth-child(2n) { background-color: #f6f8fa !important; } .markdown-preview th, .markdown-preview td { border-color: #ccc !important; } .copy-code-button { display: none !important; } .raw-text-print { margin: 0 !important; padding: 2cm !important; white-space: pre-wrap !important; word-wrap: break-word !important; font-family: 'Menlo', 'Monaco', 'Consolas', 'Courier New', monospace; font-size: 10pt !important; color: #000 !important; background: #fff !important; } pre, blockquote, table, img, h1, h2, h3, h4 { page-break-inside: avoid; } h1, h2, h3 { page-break-after: avoid; } }
        .sn-image-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); z-index: 9999; display: flex; align-items: center; justify-content: center; }
        .sn-image-modal-content { background-color: var(--sn-stylekit-background-color, #fff); color: var(--sn-stylekit-foreground-color, #333); padding: 20px; border-radius: 8px; width: 90%; max-width: 500px; box-shadow: 0 5px 15px rgba(0,0,0,0.3); }
        .sn-image-modal-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--sn-stylekit-border-color, #eee); padding-bottom: 10px; margin-bottom: 20px; }
        .sn-image-modal-header h3 { margin: 0; font-size: 18px; }
        .sn-image-modal-close { background: none; border: none; font-size: 24px; cursor: pointer; color: var(--sn-stylekit-secondary-foreground-color, #888); padding: 0 8px; }
        .sn-image-modal-tabs { display: flex; border-bottom: 1px solid var(--sn-stylekit-border-color, #ccc); margin-bottom: 20px; }
        .sn-image-modal-tab { padding: 10px 15px; cursor: pointer; border: none; background: none; border-bottom: 3px solid transparent; font-size: 15px; color: var(--sn-stylekit-secondary-foreground-color, #666); }
        .sn-image-modal-tab.active { color: var(--sn-stylekit-primary-color, #346df1); border-bottom-color: var(--sn-stylekit-primary-color, #346df1); }
        .sn-image-modal-panel { display: none; }
        .sn-image-modal-panel.active { display: block; }
        .sn-image-modal-body label { display: block; margin-bottom: 8px; font-weight: bold; font-size: 14px; }
        .sn-image-modal-body input[type="text"], .sn-image-modal-body input[type="file"] { width: 100%; padding: 8px; border-radius: 4px; border: 1px solid var(--sn-stylekit-border-color, #ccc); background-color: var(--sn-stylekit-editor-background-color, #f9f9f9); color: var(--sn-stylekit-foreground-color, #333); box-sizing: border-box; margin-bottom: 15px; }
        .sn-image-modal-footer { text-align: right; margin-top: 20px; }
        .sn-image-modal-insert-btn { padding: 8px 16px; border-radius: 5px; border: none; background-color: var(--sn-stylekit-primary-color, #346df1); color: var(--sn-stylekit-primary-contrast-color, #fff); cursor: pointer; }
        .sn-image-modal-insert-btn:disabled { background-color: var(--sn-stylekit-secondary-background-color, #f0f0f0); color: var(--sn-stylekit-secondary-foreground-color, #888); cursor: not-allowed; }
        .sn-image-upload-preview { margin-top: 10px; max-height: 150px; text-align: center; }
        .sn-image-upload-preview img { max-width: 100%; max-height: 150px; border-radius: 4px; border: 1px solid var(--sn-stylekit-border-color, #ccc); }
    `);

      function debounce(func, wait) {
          let timeout;
          return function executedFunction(...args) {
              const later = () => {
                  clearTimeout(timeout);
                  func(...args);
              };
              clearTimeout(timeout);
              timeout = setTimeout(later, wait);
          };
      }

      function applyMarkdown(textarea, prefix, suffix = '', placeholder = '') {
          const start = textarea.selectionStart; const end = textarea.selectionEnd; const selectedText = textarea.value.substring(start, end);
          const textBefore = textarea.value.substring(start - prefix.length, start); const textAfter = textarea.value.substring(end, end + suffix.length);
          if (textBefore === prefix && textAfter === suffix) { textarea.setRangeText(selectedText, start - prefix.length, end + suffix.length, 'select'); }
          else if (selectedText.startsWith(prefix) && selectedText.endsWith(suffix)) { const unwrappedText = selectedText.substring(prefix.length, selectedText.length - suffix.length); textarea.setRangeText(unwrappedText, start, end, 'select'); }
          else { let newText; if (selectedText) { newText = prefix + selectedText + suffix; } else { newText = prefix + placeholder + suffix; } textarea.setRangeText(newText, start, end, 'end'); if (!selectedText && placeholder) { textarea.selectionStart = start + prefix.length; textarea.selectionEnd = start + prefix.length + placeholder.length; } }
          textarea.focus(); textarea.dispatchEvent(new Event('input', { bubbles: true }));
      }

      function setupMarkdownEditor(originalTextarea) {
          if (originalTextarea.dataset.markdownReady) return;
          originalTextarea.dataset.markdownReady = 'true';

          marked.setOptions({ gfm: true, breaks: true, smartLists: true, langPrefix: 'language-' });

          const editorWrapper = originalTextarea.parentElement;
          editorWrapper.style.display = 'none';
          editorWrapper.style.height = '100%';

          const resizeAndEncodeImage = (file) => {
              return new Promise((resolve, reject) => {
                  if (!file.type.startsWith('image/')) {
                      return reject(new Error('File is not an image.'));
                  }
                  const reader = new FileReader();
                  reader.onload = (event) => {
                      const img = new Image();
                      img.onload = () => {
                          let { width, height } = img;
                          if (width > MAX_IMAGE_DIMENSION || height > MAX_IMAGE_DIMENSION) {
                              if (width > height) {
                                  height = Math.round(height * (MAX_IMAGE_DIMENSION / width));
                                  width = MAX_IMAGE_DIMENSION;
                              } else {
                                  width = Math.round(width * (MAX_IMAGE_DIMENSION / height));
                                  height = MAX_IMAGE_DIMENSION;
                              }
                          }
                          const canvas = document.createElement('canvas');
                          canvas.width = width;
                          canvas.height = height;
                          const ctx = canvas.getContext('2d');
                          ctx.fillStyle = '#FFFFFF';
                          ctx.fillRect(0, 0, width, height);
                          ctx.drawImage(img, 0, 0, width, height);
                          const dataUrl = canvas.toDataURL('image/jpeg', JPEG_QUALITY);
                          console.log(`Image resized: original=${(file.size / 1024).toFixed(1)}KB, new=${(dataUrl.length / 1024 * 0.75).toFixed(1)}KB (approx)`);
                          resolve(dataUrl);
                      };
                      img.onerror = (err) => reject(new Error('Failed to load image.'));
                      img.src = event.target.result;
                  };
                  reader.onerror = (err) => reject(new Error('Failed to read file.'));
                  reader.readAsDataURL(file);
              });
          };

          const insertImageAsReference = (base64data, altText, textarea) => {
              const timestamp = new Date();
              const finalAltText = altText || `${T.pastedImageAltText} ${timestamp.toLocaleString(lang)}`;
              const refId = `image-ref-${timestamp.getTime()}`;
              const markdownImageRef = `![${finalAltText}][${refId}]\n`;
              const markdownImageDef = `\n\n[${refId}]: ${base64data}`;
              const start = textarea.selectionStart;
              const end = textarea.selectionEnd;
              const textBefore = textarea.value.substring(0, start);
              const textAfter = textarea.value.substring(end);
              textarea.value = textBefore + markdownImageRef + textAfter + markdownImageDef;
              const newCursorPos = start + markdownImageRef.length;
              textarea.selectionStart = textarea.selectionEnd = newCursorPos;
              textarea.dispatchEvent(new Event('input', { bubbles: true }));
              textarea.focus();
          };

          const handlePaste = async (event) => {
              const items = event.clipboardData.items;
              for (let i = 0; i < items.length; i++) {
                  if (items[i].type.startsWith('image/')) {
                      const file = items[i].getAsFile();
                      if (file) {
                          event.preventDefault();
                          try {
                              const resizedBase64 = await resizeAndEncodeImage(file);
                              insertImageAsReference(resizedBase64, null, event.target);
                          } catch (error) {
                              console.error("Image processing failed:", error);
                              alert(T.errorImageProcessing);
                          }
                      }
                      break;
                  }
              }
          };

          const garbageCollectImageReferences = () => {
              const text = markdownTextarea.value;
              const usedRefs = new Set();
              const usageRegex = /!\[.*?\]\[(image-ref-\d+?)\]/g;
              let usageMatch;
              while ((usageMatch = usageRegex.exec(text)) !== null) {
                  usedRefs.add(usageMatch[1]);
              }
              const lines = text.split('\n');
              const newLines = [];
              let definitionsRemoved = false;
              const definitionRegex = /^\[(image-ref-\d+?)\]:\s*data:image\//;
              for (const line of lines) {
                  const defMatch = line.match(definitionRegex);
                  if (defMatch) {
                      const defId = defMatch[1];
                      if (usedRefs.has(defId)) {
                          newLines.push(line);
                      } else {
                          definitionsRemoved = true;
                          console.log(`GC: Removing orphaned image reference: ${defId}`);
                      }
                  } else {
                      newLines.push(line);
                  }
              }
              if (definitionsRemoved) {
                  const cleanedText = newLines.join('\n');
                  const cursorPos = markdownTextarea.selectionStart;
                  markdownTextarea.value = cleanedText;
                  markdownTextarea.selectionStart = markdownTextarea.selectionEnd = cursorPos;
                  originalTextarea.value = markdownTextarea.value;
                  originalTextarea.dispatchEvent(new Event('input', { bubbles: true }));
              }
          };

          const debouncedGarbageCollector = debounce(garbageCollectImageReferences, 1500);

          const openImageModal = () => {
              let selectedFile = null;
              const modalOverlay = document.createElement('div');
              modalOverlay.className = 'sn-image-modal-overlay';
              modalOverlay.innerHTML = `
                <div class="sn-image-modal-content">
                    <div class="sn-image-modal-header">
                        <h3>${T.insertImage}</h3>
                        <button class="sn-image-modal-close" title="${T.close}">&times;</button>
                    </div>
                    <div class="sn-image-modal-tabs">
                        <button class="sn-image-modal-tab active" data-tab="url">${T.fromURL}</button>
                        <button class="sn-image-modal-tab" data-tab="upload">${T.uploadFile}</button>
                    </div>
                    <div class="sn-image-modal-body">
                        <div class="sn-image-modal-panel active" id="image-modal-panel-url">
                            <label for="image-url-input">${T.imageURL}</label>
                            <input type="text" id="image-url-input" placeholder="https://example.com/image.png">
                        </div>
                        <div class="sn-image-modal-panel" id="image-modal-panel-upload">
                            <input type="file" id="image-file-input" accept="image/*" style="display: none;">
                            <button type="button" onclick="document.getElementById('image-file-input').click()" class="mode-toggle-button">${T.chooseFile}</button>
                            <div class="sn-image-upload-preview"></div>
                        </div>
                        <label for="image-alt-input" style="margin-top: 15px;">${T.altText}</label>
                        <input type="text" id="image-alt-input" placeholder="${T.altText}">
                    </div>
                    <div class="sn-image-modal-footer">
                        <button class="sn-image-modal-insert-btn">${T.insert}</button>
                    </div>
                </div>`;
              document.body.appendChild(modalOverlay);
              const content = modalOverlay.querySelector('.sn-image-modal-content');
              const closeBtn = modalOverlay.querySelector('.sn-image-modal-close');
              const tabs = modalOverlay.querySelectorAll('.sn-image-modal-tab');
              const panels = modalOverlay.querySelectorAll('.sn-image-modal-panel');
              const insertBtn = modalOverlay.querySelector('.sn-image-modal-insert-btn');
              const urlInput = modalOverlay.querySelector('#image-url-input');
              const fileInput = modalOverlay.querySelector('#image-file-input');
              const altInput = modalOverlay.querySelector('#image-alt-input');
              const previewContainer = modalOverlay.querySelector('.sn-image-upload-preview');
              let activeTab = 'url';
              const closeModal = () => document.body.contains(modalOverlay) && document.body.removeChild(modalOverlay);
              closeBtn.onclick = closeModal;
              content.onclick = (e) => e.stopPropagation();
              modalOverlay.onclick = closeModal;
              tabs.forEach(tab => {
                  tab.onclick = () => {
                      tabs.forEach(t => t.classList.remove('active'));
                      panels.forEach(p => p.classList.remove('active'));
                      tab.classList.add('active');
                      activeTab = tab.dataset.tab;
                      modalOverlay.querySelector(`#image-modal-panel-${activeTab}`).classList.add('active');
                  };
              });
              fileInput.onchange = (e) => {
                  const file = e.target.files[0];
                  if (file) {
                      selectedFile = file;
                      const reader = new FileReader();
                      reader.onload = (re) => {
                          previewContainer.innerHTML = `<img src="${re.target.result}" alt="Preview">`;
                      };
                      reader.readAsDataURL(file);
                      if (!altInput.value) { altInput.value = file.name; }
                  }
              };
              insertBtn.onclick = async () => {
                  const alt = altInput.value.trim();
                  if (activeTab === 'url') {
                      const url = urlInput.value.trim();
                      if (url) {
                          applyMarkdown(markdownTextarea, '', `![${alt}](${url})`);
                          closeModal();
                      }
                  } else if (activeTab === 'upload') {
                      if (selectedFile) {
                          try {
                              insertBtn.textContent = T.processing;
                              insertBtn.disabled = true;
                              const resizedBase64 = await resizeAndEncodeImage(selectedFile);
                              insertImageAsReference(resizedBase64, alt, markdownTextarea);
                              closeModal();
                          } catch (error) {
                              console.error("Image processing failed:", error);
                              alert(T.errorImageProcessing);
                              insertBtn.textContent = T.insert;
                              insertBtn.disabled = false;
                          }
                      }
                  }
              };
              urlInput.focus();
          };

          const container = document.createElement('div'); container.className = 'markdown-editor-container';
          const modeBar = document.createElement('div'); modeBar.className = 'mode-toggle-bar';
          const editorButton = document.createElement('button'); editorButton.className = 'mode-toggle-button'; editorButton.textContent = T.editor;
          const splitButton = document.createElement('button'); splitButton.className = 'mode-toggle-button'; splitButton.textContent = T.split;
          const previewButton = document.createElement('button'); previewButton.className = 'mode-toggle-button'; previewButton.textContent = T.preview;
          const toolbarToggleButton = document.createElement('button'); toolbarToggleButton.className = 'mode-toggle-button toolbar-toggle-button'; toolbarToggleButton.title = T.toggleToolbar; toolbarToggleButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path></svg>`;
          const printButton = document.createElement('button'); printButton.className = 'mode-toggle-button pdf-export-button'; printButton.textContent = T.printPDF; printButton.title = T.exportPDF;
          const toolbar = document.createElement('div'); toolbar.className = 'markdown-toolbar';
          const markdownTextarea = document.createElement('textarea'); markdownTextarea.className = originalTextarea.className + ' custom-markdown-textarea'; markdownTextarea.value = originalTextarea.value; markdownTextarea.spellcheck = false;
          const previewPane = document.createElement('div'); previewPane.className = 'markdown-preview';

          markdownTextarea.addEventListener('paste', handlePaste);

          const toolbarButtons = [ { type: 'select', name: 'heading', options: [ { value: 'p', text: T.paragraph }, { value: 'h1', text: T.heading1 }, { value: 'h2', text: T.heading2 }, { value: 'h3', text: T.heading3 }, { value: 'h4', text: T.heading4 } ], action: (prefix) => { const start = markdownTextarea.selectionStart; let lineStart = markdownTextarea.value.lastIndexOf('\n', start - 1) + 1; let lineEnd = markdownTextarea.value.indexOf('\n', start); if (lineEnd === -1) lineEnd = markdownTextarea.value.length; const originalLine = markdownTextarea.value.substring(lineStart, lineEnd); const cleanedLine = originalLine.replace(/^\s*#+\s*/, ''); const newText = prefix ? `${prefix} ${cleanedLine}` : cleanedLine; markdownTextarea.setRangeText(newText, lineStart, lineEnd, 'end'); markdownTextarea.dispatchEvent(new Event('input', { bubbles: true })); markdownTextarea.focus(); } }, { type: 'button', name: 'B', title: T.bold, action: () => applyMarkdown(markdownTextarea, '**', '**', T.boldPlaceholder) }, { type: 'button', name: 'I', title: T.italic, action: () => applyMarkdown(markdownTextarea, '*', '*', T.italicPlaceholder) }, { type: 'button', name: 'S', title: T.strikethrough, action: () => applyMarkdown(markdownTextarea, '~~', '~~', T.strikethroughPlaceholder) }, { type: 'button', name: '`', title: T.inlineCode, action: () => applyMarkdown(markdownTextarea, '`', '`', T.codePlaceholder) }, { type: 'button', name: '“ ”', title: T.quote, action: () => applyMarkdown(markdownTextarea, '> ', '', T.quotePlaceholder) }, { type: 'button', name: '•', title: T.list, action: () => applyMarkdown(markdownTextarea, '- ', '', T.listItemPlaceholder) }, { type: 'button', name: '1.', title: T.numberedList, action: () => applyMarkdown(markdownTextarea, '1. ', '', T.listItemPlaceholder) }, { type: 'button', name: '☑', title: T.checklist, action: () => applyMarkdown(markdownTextarea, '- [ ] ', '', T.taskPlaceholder) }, { type: 'button', name: '</>', title: T.codeBlock, action: () => applyMarkdown(markdownTextarea, '```\n', '\n```', T.codePlaceholder) }, { type: 'icon-button', name: 'Image', title: T.image, icon: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"></path></svg>`, action: openImageModal }, { type: 'button', name: 'Link', title: T.link, action: () => { const url = prompt(T.linkPrompt, 'https://'); if (url) applyMarkdown(markdownTextarea, '[', `](${url})`, T.linkTextPlaceholder); }}, { type: 'button', name: T.insertTable, title: T.insertTable, action: () => applyMarkdown(markdownTextarea, '| Column 1 | Column 2 |\n|---|---|\n|  |  |\n') }, { type: 'button', name: '―', title: T.horizontalRule, action: () => applyMarkdown(markdownTextarea, '\n---\n') }, ];
          toolbarButtons.forEach(item => { if (item.type === 'select') { const select = document.createElement('select'); select.className = 'toolbar-select heading-select'; item.options.forEach(opt => { const option = document.createElement('option'); option.value = opt.value; option.textContent = opt.text; select.appendChild(option); }); select.onchange = (e) => { let prefix = ''; switch (e.target.value) { case 'h1': prefix = '#'; break; case 'h2': prefix = '##'; break; case 'h3': prefix = '###'; break; case 'h4': prefix = '####'; break; } item.action(prefix); }; toolbar.appendChild(select); } else { const button = document.createElement('button'); button.className = 'toolbar-button'; button.title = item.title; button.onclick = item.action; if (item.type === 'icon-button') { button.classList.add('icon-button'); button.innerHTML = item.icon; } else { button.textContent = item.name; } toolbar.appendChild(button); } });

          const contentWrapper = document.createElement('div'); contentWrapper.className = 'editor-preview-wrapper';
          contentWrapper.append(markdownTextarea, previewPane);
          modeBar.append(editorButton, splitButton, previewButton, toolbarToggleButton, printButton);
          container.append(modeBar, toolbar, contentWrapper);
          editorWrapper.after(container);

          const updatePreview = () => { try { const dirtyHtml = marked.parse(markdownTextarea.value); const sanitizedHtml = purify.sanitize(dirtyHtml, { USE_PROFILES: { html: true }, ADD_ATTR: ['class', 'type', 'disabled', 'checked'], ADD_TAGS: ['span', 'input'], }); previewPane.innerHTML = sanitizedHtml; previewPane.querySelectorAll('pre code').forEach(HighlightJS.highlightElement); previewPane.querySelectorAll('pre').forEach(preEl => { if (preEl.querySelector('.copy-code-button')) return; const codeEl = preEl.querySelector('code'); if (!codeEl) return; const copyButton = document.createElement('button'); copyButton.className = 'copy-code-button'; copyButton.textContent = T.copy; copyButton.setAttribute('aria-label', T.copyAriaLabel); preEl.appendChild(copyButton); copyButton.addEventListener('click', (e) => { e.stopPropagation(); navigator.clipboard.writeText(codeEl.innerText).then(() => { copyButton.textContent = T.copied; copyButton.classList.add('copied'); setTimeout(() => { copyButton.textContent = T.copy; copyButton.classList.remove('copied'); }, 2000); }).catch(err => { console.error('Failed to copy code block.', err); copyButton.textContent = T.copyError; setTimeout(() => { copyButton.textContent = T.copy; }, 2000); }); }); }); } catch (e) { console.error("Error updating preview:", e); previewPane.innerHTML = `<div style="padding: 1rem; color: #d73a49; background-color: #f8d7da; border: 1px solid #f5c6cb; border-radius: .25rem;"><strong>${T.previewErrorTitle}</strong><br><pre style="white-space: pre-wrap; word-break: break-all; margin-top: 0.5rem;">${e.stack}</pre></div>`; } };
          const updateToolbarState = () => { const headerSelect = toolbar.querySelector('.heading-select'); if (!headerSelect) return; const start = markdownTextarea.selectionStart; const lineStart = markdownTextarea.value.lastIndexOf('\n', start - 1) + 1; const currentLine = markdownTextarea.value.substring(lineStart).split('\n')[0]; let currentStyle = 'p'; if (/^#\s/.test(currentLine)) { currentStyle = 'h1'; } else if (/^##\s/.test(currentLine)) { currentStyle = 'h2'; } else if (/^###\s/.test(currentLine)) { currentStyle = 'h3'; } else if (/^####\s/.test(currentLine)) { currentStyle = 'h4'; } if (headerSelect.value !== currentStyle) { headerSelect.value = currentStyle; } };
          markdownTextarea.addEventListener('keyup', updateToolbarState);
          markdownTextarea.addEventListener('mouseup', updateToolbarState);
          markdownTextarea.addEventListener('focus', updateToolbarState);
          markdownTextarea.addEventListener('input', () => {
              updateToolbarState();
              originalTextarea.value = markdownTextarea.value;
              originalTextarea.dispatchEvent(new Event('input', { bubbles: true }));
              if (container.classList.contains('mode-split') || container.classList.contains('mode-preview')) { updatePreview(); }
              debouncedGarbageCollector();
          });
          const observer = new MutationObserver(() => {
              if (originalTextarea.value !== markdownTextarea.value) {
                  const cursorPos = markdownTextarea.selectionStart;
                  markdownTextarea.value = originalTextarea.value;
                  markdownTextarea.selectionStart = markdownTextarea.selectionEnd = cursorPos;
                  updateToolbarState();
                  if(container.classList.contains('mode-split') || container.classList.contains('mode-preview')) { updatePreview(); }
                  debouncedGarbageCollector();
              }
          });
          observer.observe(originalTextarea, { attributes: true, childList: true, subtree: true, characterData: true });

          const modeButtons = { editor: editorButton, split: splitButton, preview: previewButton };
          const switchMode = (mode) => { container.classList.remove('mode-editor', 'mode-split', 'mode-preview'); container.classList.add(`mode-${mode}`); Object.values(modeButtons).forEach(btn => btn.classList.remove('active')); modeButtons[mode].classList.add('active'); localStorage.setItem(STORAGE_KEY_MODE, mode); if (mode === 'preview' || mode === 'split') { updatePreview(); } if (mode !== 'preview') { markdownTextarea.focus(); } };
          editorButton.addEventListener('click', () => switchMode('editor')); splitButton.addEventListener('click', () => switchMode('split')); previewButton.addEventListener('click', () => switchMode('preview'));

          const toggleToolbar = (visible) => { container.classList.toggle('toolbar-hidden', !visible); toolbarToggleButton.classList.toggle('active', visible); localStorage.setItem(STORAGE_KEY_TOOLBAR_VISIBLE, visible); };
          toolbarToggleButton.addEventListener('click', () => { const isVisible = container.classList.contains('toolbar-hidden'); toggleToolbar(isVisible); });

          const handlePrint = () => { const printContainer = document.createElement('div'); printContainer.className = 'print-container'; if (container.classList.contains('mode-editor')) { const pre = document.createElement('pre'); pre.className = 'raw-text-print'; pre.textContent = markdownTextarea.value; printContainer.appendChild(pre); } else { updatePreview(); const previewClone = previewPane.cloneNode(true); printContainer.appendChild(previewClone); } document.body.appendChild(printContainer); window.print(); document.body.removeChild(printContainer); };
          printButton.addEventListener('click', handlePrint);

          const initialToolbarVisible = localStorage.getItem(STORAGE_KEY_TOOLBAR_VISIBLE) !== 'false';
          toggleToolbar(initialToolbarVisible);
          const savedMode = localStorage.getItem(STORAGE_KEY_MODE);
          switchMode(savedMode || 'editor');

          console.log('Markdown Editor for Standard Notes has been initialized (v2.6.0 with Auto Image Resizing).');
      }

      const mainObserver = new MutationObserver(() => {
          const editor = document.querySelector('#note-text-editor');
          const customEditor = document.querySelector('.markdown-editor-container');
          if (editor && !editor.dataset.markdownReady) {
              if(customEditor) customEditor.remove();
              setupMarkdownEditor(editor);
          } else if (!editor && customEditor) {
              customEditor.remove();
              const hiddenWrapper = document.querySelector('#editor-content[style*="display: none"]');
              if(hiddenWrapper) hiddenWrapper.style.display = '';
          }
      });
      mainObserver.observe(document.body, { childList: true, subtree: true });

  })();

})();