1 line
8.1 KiB
JavaScript
1 line
8.1 KiB
JavaScript
import{EditorSelection,Prec,EditorState}from"@codemirror/state";import{keymap}from"@codemirror/view";import{syntaxTree,LanguageSupport,Language,defineLanguageFacet,foldNodeProp,indentNodeProp,languageDataProp,foldService,LanguageDescription,ParseContext}from"@codemirror/language";import{CompletionContext}from"@codemirror/autocomplete";import{MarkdownParser,parseCode,parser,GFM,Subscript,Superscript,Emoji}from"@lezer/markdown";import{htmlCompletionSource,html}from"@codemirror/lang-html";import{NodeProp}from"@lezer/common";const data=defineLanguageFacet({commentTokens:{block:{open:"\x3c!--",close:"--\x3e"}}}),headingProp=new NodeProp,commonmark=parser.configure({props:[foldNodeProp.add((e=>!e.is("Block")||e.is("Document")||null!=isHeading(e)?void 0:(e,t)=>({from:t.doc.lineAt(e.from).to,to:e.to}))),headingProp.add(isHeading),indentNodeProp.add({Document:()=>null}),languageDataProp.add({Document:data})]});function isHeading(e){let t=/^(?:ATX|Setext)Heading(\d)$/.exec(e.name);return t?+t[1]:void 0}function findSectionEnd(e,t){let r=e;for(;;){let e,n=r.nextSibling;if(!n||null!=(e=isHeading(n.type))&&e<=t)break;r=n}return r.to}const headerIndent=foldService.of(((e,t,r)=>{for(let n=syntaxTree(e).resolveInner(r,-1);n&&!(n.from<t);n=n.parent){let e=n.type.prop(headingProp);if(null==e)continue;let t=findSectionEnd(n,e);if(t>r)return{from:r,to:t}}return null}));function mkLang(e){return new Language(data,e,[headerIndent],"markdown")}const commonmarkLanguage=mkLang(commonmark),extended=commonmark.configure([GFM,Subscript,Superscript,Emoji]),markdownLanguage=mkLang(extended);function getCodeParser(e,t){return r=>{if(r&&e){let t=null;if(r=/\S*/.exec(r)[0],t="function"==typeof e?e(r):LanguageDescription.matchLanguageName(e,r,!0),t instanceof LanguageDescription)return t.support?t.support.language.parser:ParseContext.getSkippingParser(t.load());if(t)return t.parser}return t?t.parser:null}}class Context{constructor(e,t,r,n,o,a,i){this.node=e,this.from=t,this.to=r,this.spaceBefore=n,this.spaceAfter=o,this.type=a,this.item=i}blank(e,t=!0){let r=this.spaceBefore+("Blockquote"==this.node.name?">":"");if(null!=e){for(;r.length<e;)r+=" ";return r}for(let e=this.to-this.from-r.length-this.spaceAfter.length;e>0;e--)r+=" ";return r+(t?this.spaceAfter:"")}marker(e,t){let r="OrderedList"==this.node.name?String(+itemNumber(this.item,e)[2]+t):"";return this.spaceBefore+r+this.type+this.spaceAfter}}function getContext(e,t){let r=[];for(let t=e;t&&"Document"!=t.name;t=t.parent)"ListItem"!=t.name&&"Blockquote"!=t.name&&"FencedCode"!=t.name||r.push(t);let n=[];for(let e=r.length-1;e>=0;e--){let o,a=r[e],i=t.lineAt(a.from),l=a.from-i.from;if("FencedCode"==a.name)n.push(new Context(a,l,l,"","","",null));else if("Blockquote"==a.name&&(o=/^[ \t]*>( ?)/.exec(i.text.slice(l))))n.push(new Context(a,l,l+o[0].length,"",o[1],">",null));else if("ListItem"==a.name&&"OrderedList"==a.parent.name&&(o=/^([ \t]*)\d+([.)])([ \t]*)/.exec(i.text.slice(l)))){let e=o[3],t=o[0].length;e.length>=4&&(e=e.slice(0,e.length-4),t-=4),n.push(new Context(a.parent,l,l+t,o[1],e,o[2],a))}else if("ListItem"==a.name&&"BulletList"==a.parent.name&&(o=/^([ \t]*)([-+*])([ \t]{1,4}\[[ xX]\])?([ \t]+)/.exec(i.text.slice(l)))){let e=o[4],t=o[0].length;e.length>4&&(e=e.slice(0,e.length-4),t-=4);let r=o[2];o[3]&&(r+=o[3].replace(/[xX]/," ")),n.push(new Context(a.parent,l,l+t,o[1],e,r,a))}}return n}function itemNumber(e,t){return/^(\s*)(\d+)(?=[.)])/.exec(t.sliceString(e.from,e.from+10))}function renumberList(e,t,r,n=0){for(let o=-1,a=e;;){if("ListItem"==a.name){let e=itemNumber(a,t),i=+e[2];if(o>=0){if(i!=o+1)return;r.push({from:a.from+e[1].length,to:a.from+e[0].length,insert:String(o+2+n)})}o=i}let e=a.nextSibling;if(!e)break;a=e}}const insertNewlineContinueMarkup=({state:e,dispatch:t})=>{let r=syntaxTree(e),{doc:n}=e,o=null,a=e.changeByRange((t=>{if(!t.empty||!markdownLanguage.isActiveAt(e,t.from))return o={range:t};let a=t.from,i=n.lineAt(a),l=getContext(r.resolveInner(a,-1),n);for(;l.length&&l[l.length-1].from>a-i.from;)l.pop();if(!l.length)return o={range:t};let m=l[l.length-1];if(m.to-m.spaceAfter.length>a-i.from)return o={range:t};let s=a>=m.to-m.spaceAfter.length&&!/\S/.test(i.text.slice(m.to));if(m.item&&s){if(m.node.firstChild.to>=a||i.from>0&&!/[^\s>]/.test(n.lineAt(i.from-1).text)){let e,t=l.length>1?l[l.length-2]:null,r="";t&&t.item?(e=i.from+t.from,r=t.marker(n,1)):e=i.from+(t?t.to:0);let o=[{from:e,to:a,insert:r}];return"OrderedList"==m.node.name&&renumberList(m.item,n,o,-2),t&&"OrderedList"==t.node.name&&renumberList(t.item,n,o),{range:EditorSelection.cursor(e+r.length),changes:o}}{let t="";for(let e=0,r=l.length-2;e<=r;e++)t+=l[e].blank(e<r?l[e+1].from-t.length:null,e<r);return t+=e.lineBreak,{range:EditorSelection.cursor(a+t.length),changes:{from:i.from,insert:t}}}}if("Blockquote"==m.node.name&&s&&i.from){let r=n.lineAt(i.from-1),o=/>\s*$/.exec(r.text);if(o&&o.index==m.from){let n=e.changes([{from:r.from+o.index,to:r.to},{from:i.from+m.from,to:i.to}]);return{range:t.map(n),changes:n}}}let f=[];"OrderedList"==m.node.name&&renumberList(m.item,n,f);let u=m.item&&m.item.from<i.from,c="";if(!u||/^[\s\d.)\-+*>]*/.exec(i.text)[0].length>=m.to)for(let e=0,t=l.length-1;e<=t;e++)c+=e!=t||u?l[e].blank(e<t?l[e+1].from-c.length:null):l[e].marker(n,1);let g=a;for(;g>i.from&&/\s/.test(i.text.charAt(g-i.from-1));)g--;return c=e.lineBreak+c,f.push({from:g,to:a,insert:c}),{range:EditorSelection.cursor(g+c.length),changes:f}}));return!o&&(t(e.update(a,{scrollIntoView:!0,userEvent:"input"})),!0)};function isMark(e){return"QuoteMark"==e.name||"ListMark"==e.name}function contextNodeForDelete(e,t){let r=e.resolveInner(t,-1),n=t;isMark(r)&&(n=r.from,r=r.parent);for(let e;e=r.childBefore(n);)if(isMark(e))n=e.from;else{if("OrderedList"!=e.name&&"BulletList"!=e.name)break;r=e.lastChild,n=r.to}return r}const deleteMarkupBackward=({state:e,dispatch:t})=>{let r=syntaxTree(e),n=null,o=e.changeByRange((t=>{let o=t.from,{doc:a}=e;if(t.empty&&markdownLanguage.isActiveAt(e,t.from)){let e=a.lineAt(o),n=getContext(contextNodeForDelete(r,o),a);if(n.length){let r=n[n.length-1],a=r.to-r.spaceAfter.length+(r.spaceAfter?1:0);if(o-e.from>a&&!/\S/.test(e.text.slice(a,o-e.from)))return{range:EditorSelection.cursor(e.from+a),changes:{from:e.from+a,to:o}};if(o-e.from==a&&(!r.item||e.from<=r.item.from||!/\S/.test(e.text.slice(0,r.to)))){let n=e.from+r.from;if(r.item&&r.node.from<r.item.from&&/\S/.test(e.text.slice(r.from,r.to)))return{range:t,changes:{from:n,to:e.from+r.to,insert:r.blank(r.to-r.from)}};if(n<o)return{range:EditorSelection.cursor(n),changes:{from:n,to:o}}}}}return n={range:t}}));return!n&&(t(e.update(o,{scrollIntoView:!0,userEvent:"delete"})),!0)},markdownKeymap=[{key:"Enter",run:insertNewlineContinueMarkup},{key:"Backspace",run:deleteMarkupBackward}],htmlNoMatch=html({matchClosingTags:!1});function markdown(e={}){let{codeLanguages:t,defaultCodeLanguage:r,addKeymap:n=!0,base:{parser:o}=commonmarkLanguage,completeHTMLTags:a=!0}=e;if(!(o instanceof MarkdownParser))throw new RangeError("Base parser provided to `markdown` should be a Markdown parser");let i,l=e.extensions?[e.extensions]:[],m=[htmlNoMatch.support];r instanceof LanguageSupport?(m.push(r.support),i=r.language):r&&(i=r);let s=t||i?getCodeParser(t,i):void 0;l.push(parseCode({codeParser:s,htmlParser:htmlNoMatch.language.parser})),n&&m.push(Prec.high(keymap.of(markdownKeymap)));let f=mkLang(o.configure(l));return a&&m.push(f.data.of({autocomplete:htmlTagCompletion})),new LanguageSupport(f,m)}function htmlTagCompletion(e){let{state:t,pos:r}=e,n=/<[:\-\.\w\u00b7-\uffff]*$/.exec(t.sliceDoc(r-25,r));if(!n)return null;let o=syntaxTree(t).resolveInner(r,-1);for(;o&&!o.type.isTop;){if("CodeBlock"==o.name||"FencedCode"==o.name||"ProcessingInstructionBlock"==o.name||"CommentBlock"==o.name||"Link"==o.name||"Image"==o.name)return null;o=o.parent}return{from:r-n[0].length,to:r,options:htmlTagCompletions(),validFor:/^<[:\-\.\w\u00b7-\uffff]*$/}}let _tagCompletions=null;function htmlTagCompletions(){if(_tagCompletions)return _tagCompletions;let e=htmlCompletionSource(new CompletionContext(EditorState.create({extensions:htmlNoMatch}),0,!0));return _tagCompletions=e?e.options:[]}export{commonmarkLanguage,deleteMarkupBackward,insertNewlineContinueMarkup,markdown,markdownKeymap,markdownLanguage}; |