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