Diffdown is a real-time collaborative Markdown editor/previewer built on the AT Protocol diffdown.com
1import { parser, configureNesting } from '@lezer/html'; 2import { cssLanguage, css } from '@codemirror/lang-css'; 3import { javascriptLanguage, typescriptLanguage, jsxLanguage, tsxLanguage, javascript } from '@codemirror/lang-javascript'; 4import { EditorView } from '@codemirror/view'; 5import { EditorSelection } from '@codemirror/state'; 6import { syntaxTree, LRLanguage, indentNodeProp, foldNodeProp, bracketMatchingHandle, LanguageSupport } from '@codemirror/language'; 7 8const Targets = ["_blank", "_self", "_top", "_parent"]; 9const Charsets = ["ascii", "utf-8", "utf-16", "latin1", "latin1"]; 10const Methods = ["get", "post", "put", "delete"]; 11const Encs = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"]; 12const Bool = ["true", "false"]; 13const S = {}; // Empty tag spec 14const Tags = { 15 a: { 16 attrs: { 17 href: null, ping: null, type: null, 18 media: null, 19 target: Targets, 20 hreflang: null 21 } 22 }, 23 abbr: S, 24 address: S, 25 area: { 26 attrs: { 27 alt: null, coords: null, href: null, target: null, ping: null, 28 media: null, hreflang: null, type: null, 29 shape: ["default", "rect", "circle", "poly"] 30 } 31 }, 32 article: S, 33 aside: S, 34 audio: { 35 attrs: { 36 src: null, mediagroup: null, 37 crossorigin: ["anonymous", "use-credentials"], 38 preload: ["none", "metadata", "auto"], 39 autoplay: ["autoplay"], 40 loop: ["loop"], 41 controls: ["controls"] 42 } 43 }, 44 b: S, 45 base: { attrs: { href: null, target: Targets } }, 46 bdi: S, 47 bdo: S, 48 blockquote: { attrs: { cite: null } }, 49 body: S, 50 br: S, 51 button: { 52 attrs: { 53 form: null, formaction: null, name: null, value: null, 54 autofocus: ["autofocus"], 55 disabled: ["autofocus"], 56 formenctype: Encs, 57 formmethod: Methods, 58 formnovalidate: ["novalidate"], 59 formtarget: Targets, 60 type: ["submit", "reset", "button"] 61 } 62 }, 63 canvas: { attrs: { width: null, height: null } }, 64 caption: S, 65 center: S, 66 cite: S, 67 code: S, 68 col: { attrs: { span: null } }, 69 colgroup: { attrs: { span: null } }, 70 command: { 71 attrs: { 72 type: ["command", "checkbox", "radio"], 73 label: null, icon: null, radiogroup: null, command: null, title: null, 74 disabled: ["disabled"], 75 checked: ["checked"] 76 } 77 }, 78 data: { attrs: { value: null } }, 79 datagrid: { attrs: { disabled: ["disabled"], multiple: ["multiple"] } }, 80 datalist: { attrs: { data: null } }, 81 dd: S, 82 del: { attrs: { cite: null, datetime: null } }, 83 details: { attrs: { open: ["open"] } }, 84 dfn: S, 85 div: S, 86 dl: S, 87 dt: S, 88 em: S, 89 embed: { attrs: { src: null, type: null, width: null, height: null } }, 90 eventsource: { attrs: { src: null } }, 91 fieldset: { attrs: { disabled: ["disabled"], form: null, name: null } }, 92 figcaption: S, 93 figure: S, 94 footer: S, 95 form: { 96 attrs: { 97 action: null, name: null, 98 "accept-charset": Charsets, 99 autocomplete: ["on", "off"], 100 enctype: Encs, 101 method: Methods, 102 novalidate: ["novalidate"], 103 target: Targets 104 } 105 }, 106 h1: S, h2: S, h3: S, h4: S, h5: S, h6: S, 107 head: { 108 children: ["title", "base", "link", "style", "meta", "script", "noscript", "command"] 109 }, 110 header: S, 111 hgroup: S, 112 hr: S, 113 html: { 114 attrs: { manifest: null } 115 }, 116 i: S, 117 iframe: { 118 attrs: { 119 src: null, srcdoc: null, name: null, width: null, height: null, 120 sandbox: ["allow-top-navigation", "allow-same-origin", "allow-forms", "allow-scripts"], 121 seamless: ["seamless"] 122 } 123 }, 124 img: { 125 attrs: { 126 alt: null, src: null, ismap: null, usemap: null, width: null, height: null, 127 crossorigin: ["anonymous", "use-credentials"] 128 } 129 }, 130 input: { 131 attrs: { 132 alt: null, dirname: null, form: null, formaction: null, 133 height: null, list: null, max: null, maxlength: null, min: null, 134 name: null, pattern: null, placeholder: null, size: null, src: null, 135 step: null, value: null, width: null, 136 accept: ["audio/*", "video/*", "image/*"], 137 autocomplete: ["on", "off"], 138 autofocus: ["autofocus"], 139 checked: ["checked"], 140 disabled: ["disabled"], 141 formenctype: Encs, 142 formmethod: Methods, 143 formnovalidate: ["novalidate"], 144 formtarget: Targets, 145 multiple: ["multiple"], 146 readonly: ["readonly"], 147 required: ["required"], 148 type: ["hidden", "text", "search", "tel", "url", "email", "password", "datetime", "date", "month", 149 "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio", 150 "file", "submit", "image", "reset", "button"] 151 } 152 }, 153 ins: { attrs: { cite: null, datetime: null } }, 154 kbd: S, 155 keygen: { 156 attrs: { 157 challenge: null, form: null, name: null, 158 autofocus: ["autofocus"], 159 disabled: ["disabled"], 160 keytype: ["RSA"] 161 } 162 }, 163 label: { attrs: { for: null, form: null } }, 164 legend: S, 165 li: { attrs: { value: null } }, 166 link: { 167 attrs: { 168 href: null, type: null, 169 hreflang: null, 170 media: null, 171 sizes: ["all", "16x16", "16x16 32x32", "16x16 32x32 64x64"] 172 } 173 }, 174 map: { attrs: { name: null } }, 175 mark: S, 176 menu: { attrs: { label: null, type: ["list", "context", "toolbar"] } }, 177 meta: { 178 attrs: { 179 content: null, 180 charset: Charsets, 181 name: ["viewport", "application-name", "author", "description", "generator", "keywords"], 182 "http-equiv": ["content-language", "content-type", "default-style", "refresh"] 183 } 184 }, 185 meter: { attrs: { value: null, min: null, low: null, high: null, max: null, optimum: null } }, 186 nav: S, 187 noscript: S, 188 object: { 189 attrs: { 190 data: null, type: null, name: null, usemap: null, form: null, width: null, height: null, 191 typemustmatch: ["typemustmatch"] 192 } 193 }, 194 ol: { attrs: { reversed: ["reversed"], start: null, type: ["1", "a", "A", "i", "I"] }, 195 children: ["li", "script", "template", "ul", "ol"] }, 196 optgroup: { attrs: { disabled: ["disabled"], label: null } }, 197 option: { attrs: { disabled: ["disabled"], label: null, selected: ["selected"], value: null } }, 198 output: { attrs: { for: null, form: null, name: null } }, 199 p: S, 200 param: { attrs: { name: null, value: null } }, 201 pre: S, 202 progress: { attrs: { value: null, max: null } }, 203 q: { attrs: { cite: null } }, 204 rp: S, 205 rt: S, 206 ruby: S, 207 samp: S, 208 script: { 209 attrs: { 210 type: ["text/javascript"], 211 src: null, 212 async: ["async"], 213 defer: ["defer"], 214 charset: Charsets 215 } 216 }, 217 section: S, 218 select: { 219 attrs: { 220 form: null, name: null, size: null, 221 autofocus: ["autofocus"], 222 disabled: ["disabled"], 223 multiple: ["multiple"] 224 } 225 }, 226 slot: { attrs: { name: null } }, 227 small: S, 228 source: { attrs: { src: null, type: null, media: null } }, 229 span: S, 230 strong: S, 231 style: { 232 attrs: { 233 type: ["text/css"], 234 media: null, 235 scoped: null 236 } 237 }, 238 sub: S, 239 summary: S, 240 sup: S, 241 table: S, 242 tbody: S, 243 td: { attrs: { colspan: null, rowspan: null, headers: null } }, 244 template: S, 245 textarea: { 246 attrs: { 247 dirname: null, form: null, maxlength: null, name: null, placeholder: null, 248 rows: null, cols: null, 249 autofocus: ["autofocus"], 250 disabled: ["disabled"], 251 readonly: ["readonly"], 252 required: ["required"], 253 wrap: ["soft", "hard"] 254 } 255 }, 256 tfoot: S, 257 th: { attrs: { colspan: null, rowspan: null, headers: null, scope: ["row", "col", "rowgroup", "colgroup"] } }, 258 thead: S, 259 time: { attrs: { datetime: null } }, 260 title: S, 261 tr: S, 262 track: { 263 attrs: { 264 src: null, label: null, default: null, 265 kind: ["subtitles", "captions", "descriptions", "chapters", "metadata"], 266 srclang: null 267 } 268 }, 269 ul: { children: ["li", "script", "template", "ul", "ol"] }, 270 var: S, 271 video: { 272 attrs: { 273 src: null, poster: null, width: null, height: null, 274 crossorigin: ["anonymous", "use-credentials"], 275 preload: ["auto", "metadata", "none"], 276 autoplay: ["autoplay"], 277 mediagroup: ["movie"], 278 muted: ["muted"], 279 controls: ["controls"] 280 } 281 }, 282 wbr: S 283}; 284const GlobalAttrs = { 285 accesskey: null, 286 class: null, 287 contenteditable: Bool, 288 contextmenu: null, 289 dir: ["ltr", "rtl", "auto"], 290 draggable: ["true", "false", "auto"], 291 dropzone: ["copy", "move", "link", "string:", "file:"], 292 hidden: ["hidden"], 293 id: null, 294 inert: ["inert"], 295 itemid: null, 296 itemprop: null, 297 itemref: null, 298 itemscope: ["itemscope"], 299 itemtype: null, 300 lang: ["ar", "bn", "de", "en-GB", "en-US", "es", "fr", "hi", "id", "ja", "pa", "pt", "ru", "tr", "zh"], 301 spellcheck: Bool, 302 autocorrect: Bool, 303 autocapitalize: Bool, 304 style: null, 305 tabindex: null, 306 title: null, 307 translate: ["yes", "no"], 308 rel: ["stylesheet", "alternate", "author", "bookmark", "help", "license", "next", "nofollow", "noreferrer", "prefetch", "prev", "search", "tag"], 309 role: /*@__PURE__*/"alert application article banner button cell checkbox complementary contentinfo dialog document feed figure form grid gridcell heading img list listbox listitem main navigation region row rowgroup search switch tab table tabpanel textbox timer".split(" "), 310 "aria-activedescendant": null, 311 "aria-atomic": Bool, 312 "aria-autocomplete": ["inline", "list", "both", "none"], 313 "aria-busy": Bool, 314 "aria-checked": ["true", "false", "mixed", "undefined"], 315 "aria-controls": null, 316 "aria-describedby": null, 317 "aria-disabled": Bool, 318 "aria-dropeffect": null, 319 "aria-expanded": ["true", "false", "undefined"], 320 "aria-flowto": null, 321 "aria-grabbed": ["true", "false", "undefined"], 322 "aria-haspopup": Bool, 323 "aria-hidden": Bool, 324 "aria-invalid": ["true", "false", "grammar", "spelling"], 325 "aria-label": null, 326 "aria-labelledby": null, 327 "aria-level": null, 328 "aria-live": ["off", "polite", "assertive"], 329 "aria-multiline": Bool, 330 "aria-multiselectable": Bool, 331 "aria-owns": null, 332 "aria-posinset": null, 333 "aria-pressed": ["true", "false", "mixed", "undefined"], 334 "aria-readonly": Bool, 335 "aria-relevant": null, 336 "aria-required": Bool, 337 "aria-selected": ["true", "false", "undefined"], 338 "aria-setsize": null, 339 "aria-sort": ["ascending", "descending", "none", "other"], 340 "aria-valuemax": null, 341 "aria-valuemin": null, 342 "aria-valuenow": null, 343 "aria-valuetext": null 344}; 345const eventAttributes = /*@__PURE__*/("beforeunload copy cut dragstart dragover dragleave dragenter dragend " + 346 "drag paste focus blur change click load mousedown mouseenter mouseleave " + 347 "mouseup keydown keyup resize scroll unload").split(" ").map(n => "on" + n); 348for (let a of eventAttributes) 349 GlobalAttrs[a] = null; 350class Schema { 351 constructor(extraTags, extraAttrs) { 352 this.tags = { ...Tags, ...extraTags }; 353 this.globalAttrs = { ...GlobalAttrs, ...extraAttrs }; 354 this.allTags = Object.keys(this.tags); 355 this.globalAttrNames = Object.keys(this.globalAttrs); 356 } 357} 358Schema.default = /*@__PURE__*/new Schema; 359function elementName(doc, tree, max = doc.length) { 360 if (!tree) 361 return ""; 362 let tag = tree.firstChild; 363 let name = tag && tag.getChild("TagName"); 364 return name ? doc.sliceString(name.from, Math.min(name.to, max)) : ""; 365} 366function findParentElement(tree, skip = false) { 367 for (; tree; tree = tree.parent) 368 if (tree.name == "Element") { 369 if (skip) 370 skip = false; 371 else 372 return tree; 373 } 374 return null; 375} 376function allowedChildren(doc, tree, schema) { 377 let parentInfo = schema.tags[elementName(doc, findParentElement(tree))]; 378 return (parentInfo === null || parentInfo === void 0 ? void 0 : parentInfo.children) || schema.allTags; 379} 380function openTags(doc, tree) { 381 let open = []; 382 for (let parent = findParentElement(tree); parent && !parent.type.isTop; parent = findParentElement(parent.parent)) { 383 let tagName = elementName(doc, parent); 384 if (tagName && parent.lastChild.name == "CloseTag") 385 break; 386 if (tagName && open.indexOf(tagName) < 0 && (tree.name == "EndTag" || tree.from >= parent.firstChild.to)) 387 open.push(tagName); 388 } 389 return open; 390} 391const identifier = /^[:\-\.\w\u00b7-\uffff]*$/; 392function completeTag(state, schema, tree, from, to) { 393 let end = /\s*>/.test(state.sliceDoc(to, to + 5)) ? "" : ">"; 394 let parent = findParentElement(tree, tree.name == "StartTag" || tree.name == "TagName"); 395 return { from, to, 396 options: allowedChildren(state.doc, parent, schema).map(tagName => ({ label: tagName, type: "type" })).concat(openTags(state.doc, tree).map((tag, i) => ({ label: "/" + tag, apply: "/" + tag + end, 397 type: "type", boost: 99 - i }))), 398 validFor: /^\/?[:\-\.\w\u00b7-\uffff]*$/ }; 399} 400function completeCloseTag(state, tree, from, to) { 401 let end = /\s*>/.test(state.sliceDoc(to, to + 5)) ? "" : ">"; 402 return { from, to, 403 options: openTags(state.doc, tree).map((tag, i) => ({ label: tag, apply: tag + end, type: "type", boost: 99 - i })), 404 validFor: identifier }; 405} 406function completeStartTag(state, schema, tree, pos) { 407 let options = [], level = 0; 408 for (let tagName of allowedChildren(state.doc, tree, schema)) 409 options.push({ label: "<" + tagName, type: "type" }); 410 for (let open of openTags(state.doc, tree)) 411 options.push({ label: "</" + open + ">", type: "type", boost: 99 - level++ }); 412 return { from: pos, to: pos, options, validFor: /^<\/?[:\-\.\w\u00b7-\uffff]*$/ }; 413} 414function completeAttrName(state, schema, tree, from, to) { 415 let elt = findParentElement(tree), info = elt ? schema.tags[elementName(state.doc, elt)] : null; 416 let localAttrs = info && info.attrs ? Object.keys(info.attrs) : []; 417 let names = info && info.globalAttrs === false ? localAttrs 418 : localAttrs.length ? localAttrs.concat(schema.globalAttrNames) : schema.globalAttrNames; 419 return { from, to, 420 options: names.map(attrName => ({ label: attrName, type: "property" })), 421 validFor: identifier }; 422} 423function completeAttrValue(state, schema, tree, from, to) { 424 var _a; 425 let nameNode = (_a = tree.parent) === null || _a === void 0 ? void 0 : _a.getChild("AttributeName"); 426 let options = [], token = undefined; 427 if (nameNode) { 428 let attrName = state.sliceDoc(nameNode.from, nameNode.to); 429 let attrs = schema.globalAttrs[attrName]; 430 if (!attrs) { 431 let elt = findParentElement(tree), info = elt ? schema.tags[elementName(state.doc, elt)] : null; 432 attrs = (info === null || info === void 0 ? void 0 : info.attrs) && info.attrs[attrName]; 433 } 434 if (attrs) { 435 let base = state.sliceDoc(from, to).toLowerCase(), quoteStart = '"', quoteEnd = '"'; 436 if (/^['"]/.test(base)) { 437 token = base[0] == '"' ? /^[^"]*$/ : /^[^']*$/; 438 quoteStart = ""; 439 quoteEnd = state.sliceDoc(to, to + 1) == base[0] ? "" : base[0]; 440 base = base.slice(1); 441 from++; 442 } 443 else { 444 token = /^[^\s<>='"]*$/; 445 } 446 for (let value of attrs) 447 options.push({ label: value, apply: quoteStart + value + quoteEnd, type: "constant" }); 448 } 449 } 450 return { from, to, options, validFor: token }; 451} 452function htmlCompletionFor(schema, context) { 453 let { state, pos } = context, tree = syntaxTree(state).resolveInner(pos, -1), around = tree.resolve(pos); 454 for (let scan = pos, before; around == tree && (before = tree.childBefore(scan));) { 455 let last = before.lastChild; 456 if (!last || !last.type.isError || last.from < last.to) 457 break; 458 around = tree = before; 459 scan = last.from; 460 } 461 if (tree.name == "TagName") { 462 return tree.parent && /CloseTag$/.test(tree.parent.name) ? completeCloseTag(state, tree, tree.from, pos) 463 : completeTag(state, schema, tree, tree.from, pos); 464 } 465 else if (tree.name == "StartTag" || tree.name == "IncompleteTag") { 466 return completeTag(state, schema, tree, pos, pos); 467 } 468 else if (tree.name == "StartCloseTag" || tree.name == "IncompleteCloseTag") { 469 return completeCloseTag(state, tree, pos, pos); 470 } 471 else if (tree.name == "OpenTag" || tree.name == "SelfClosingTag" || tree.name == "AttributeName") { 472 return completeAttrName(state, schema, tree, tree.name == "AttributeName" ? tree.from : pos, pos); 473 } 474 else if (tree.name == "Is" || tree.name == "AttributeValue" || tree.name == "UnquotedAttributeValue") { 475 return completeAttrValue(state, schema, tree, tree.name == "Is" ? pos : tree.from, pos); 476 } 477 else if (context.explicit && (around.name == "Element" || around.name == "Text" || around.name == "Document")) { 478 return completeStartTag(state, schema, tree, pos); 479 } 480 else { 481 return null; 482 } 483} 484/** 485HTML tag completion. Opens and closes tags and attributes in a 486context-aware way. 487*/ 488function htmlCompletionSource(context) { 489 return htmlCompletionFor(Schema.default, context); 490} 491/** 492Create a completion source for HTML extended with additional tags 493or attributes. 494*/ 495function htmlCompletionSourceWith(config) { 496 let { extraTags, extraGlobalAttributes: extraAttrs } = config; 497 let schema = extraAttrs || extraTags ? new Schema(extraTags, extraAttrs) : Schema.default; 498 return (context) => htmlCompletionFor(schema, context); 499} 500 501const jsonParser = /*@__PURE__*/javascriptLanguage.parser.configure({ top: "SingleExpression" }); 502const defaultNesting = [ 503 { tag: "script", 504 attrs: attrs => attrs.type == "text/typescript" || attrs.lang == "ts", 505 parser: typescriptLanguage.parser }, 506 { tag: "script", 507 attrs: attrs => attrs.type == "text/babel" || attrs.type == "text/jsx", 508 parser: jsxLanguage.parser }, 509 { tag: "script", 510 attrs: attrs => attrs.type == "text/typescript-jsx", 511 parser: tsxLanguage.parser }, 512 { tag: "script", 513 attrs(attrs) { 514 return /^(importmap|speculationrules|application\/(.+\+)?json)$/i.test(attrs.type); 515 }, 516 parser: jsonParser }, 517 { tag: "script", 518 attrs(attrs) { 519 return !attrs.type || /^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i.test(attrs.type); 520 }, 521 parser: javascriptLanguage.parser }, 522 { tag: "style", 523 attrs(attrs) { 524 return (!attrs.lang || attrs.lang == "css") && (!attrs.type || /^(text\/)?(x-)?(stylesheet|css)$/i.test(attrs.type)); 525 }, 526 parser: cssLanguage.parser } 527]; 528const defaultAttrs = /*@__PURE__*/[ 529 { name: "style", 530 parser: /*@__PURE__*/cssLanguage.parser.configure({ top: "Styles" }) } 531].concat(/*@__PURE__*/eventAttributes.map(name => ({ name, parser: javascriptLanguage.parser }))); 532const htmlPlain = /*@__PURE__*/LRLanguage.define({ 533 name: "html", 534 parser: /*@__PURE__*/parser.configure({ 535 props: [ 536 /*@__PURE__*/indentNodeProp.add({ 537 Element(context) { 538 let after = /^(\s*)(<\/)?/.exec(context.textAfter); 539 if (context.node.to <= context.pos + after[0].length) 540 return context.continue(); 541 return context.lineIndent(context.node.from) + (after[2] ? 0 : context.unit); 542 }, 543 "OpenTag CloseTag SelfClosingTag"(context) { 544 return context.column(context.node.from) + context.unit; 545 }, 546 Document(context) { 547 if (context.pos + /\s*/.exec(context.textAfter)[0].length < context.node.to) 548 return context.continue(); 549 let endElt = null, close; 550 for (let cur = context.node;;) { 551 let last = cur.lastChild; 552 if (!last || last.name != "Element" || last.to != cur.to) 553 break; 554 endElt = cur = last; 555 } 556 if (endElt && !((close = endElt.lastChild) && (close.name == "CloseTag" || close.name == "SelfClosingTag"))) 557 return context.lineIndent(endElt.from) + context.unit; 558 return null; 559 } 560 }), 561 /*@__PURE__*/foldNodeProp.add({ 562 Element(node) { 563 let first = node.firstChild, last = node.lastChild; 564 if (!first || first.name != "OpenTag") 565 return null; 566 return { from: first.to, to: last.name == "CloseTag" ? last.from : node.to }; 567 } 568 }), 569 /*@__PURE__*/bracketMatchingHandle.add({ 570 "OpenTag CloseTag": node => node.getChild("TagName") 571 }) 572 ] 573 }), 574 languageData: { 575 commentTokens: { block: { open: "<!--", close: "-->" } }, 576 indentOnInput: /^\s*<\/\w+\W$/, 577 wordChars: "-_" 578 } 579}); 580/** 581A language provider based on the [Lezer HTML 582parser](https://github.com/lezer-parser/html), extended with the 583JavaScript and CSS parsers to parse the content of `<script>` and 584`<style>` tags. 585*/ 586const htmlLanguage = /*@__PURE__*/htmlPlain.configure({ 587 wrap: /*@__PURE__*/configureNesting(defaultNesting, defaultAttrs) 588}); 589/** 590Language support for HTML, including 591[`htmlCompletion`](https://codemirror.net/6/docs/ref/#lang-html.htmlCompletion) and JavaScript and 592CSS support extensions. 593*/ 594function html(config = {}) { 595 let dialect = "", wrap; 596 if (config.matchClosingTags === false) 597 dialect = "noMatch"; 598 if (config.selfClosingTags === true) 599 dialect = (dialect ? dialect + " " : "") + "selfClosing"; 600 if (config.nestedLanguages && config.nestedLanguages.length || 601 config.nestedAttributes && config.nestedAttributes.length) 602 wrap = configureNesting((config.nestedLanguages || []).concat(defaultNesting), (config.nestedAttributes || []).concat(defaultAttrs)); 603 let lang = wrap ? htmlPlain.configure({ wrap, dialect }) : dialect ? htmlLanguage.configure({ dialect }) : htmlLanguage; 604 return new LanguageSupport(lang, [ 605 htmlLanguage.data.of({ autocomplete: htmlCompletionSourceWith(config) }), 606 config.autoCloseTags !== false ? autoCloseTags : [], 607 javascript().support, 608 css().support 609 ]); 610} 611const selfClosers = /*@__PURE__*/new Set(/*@__PURE__*/"area base br col command embed frame hr img input keygen link meta param source track wbr menuitem".split(" ")); 612/** 613Extension that will automatically insert close tags when a `>` or 614`/` is typed. 615*/ 616const autoCloseTags = /*@__PURE__*/EditorView.inputHandler.of((view, from, to, text, insertTransaction) => { 617 if (view.composing || view.state.readOnly || from != to || (text != ">" && text != "/") || 618 !htmlLanguage.isActiveAt(view.state, from, -1)) 619 return false; 620 let base = insertTransaction(), { state } = base; 621 let closeTags = state.changeByRange(range => { 622 var _a, _b, _c; 623 let didType = state.doc.sliceString(range.from - 1, range.to) == text; 624 let { head } = range, after = syntaxTree(state).resolveInner(head, -1), name; 625 if (didType && text == ">" && after.name == "EndTag") { 626 let tag = after.parent; 627 if (((_b = (_a = tag.parent) === null || _a === void 0 ? void 0 : _a.lastChild) === null || _b === void 0 ? void 0 : _b.name) != "CloseTag" && 628 (name = elementName(state.doc, tag.parent, head)) && 629 !selfClosers.has(name)) { 630 let to = head + (state.doc.sliceString(head, head + 1) === ">" ? 1 : 0); 631 let insert = `</${name}>`; 632 return { range, changes: { from: head, to, insert } }; 633 } 634 } 635 else if (didType && text == "/" && after.name == "IncompleteCloseTag") { 636 let tag = after.parent; 637 if (after.from == head - 2 && ((_c = tag.lastChild) === null || _c === void 0 ? void 0 : _c.name) != "CloseTag" && 638 (name = elementName(state.doc, tag, head)) && !selfClosers.has(name)) { 639 let to = head + (state.doc.sliceString(head, head + 1) === ">" ? 1 : 0); 640 let insert = `${name}>`; 641 return { 642 range: EditorSelection.cursor(head + insert.length, -1), 643 changes: { from: head, to, insert } 644 }; 645 } 646 } 647 return { range }; 648 }); 649 if (closeTags.changes.empty) 650 return false; 651 view.dispatch([ 652 base, 653 state.update(closeTags, { 654 userEvent: "input.complete", 655 scrollIntoView: true 656 }) 657 ]); 658 return true; 659}); 660 661export { autoCloseTags, html, htmlCompletionSource, htmlCompletionSourceWith, htmlLanguage };