Ivan Asmer před 4 roky
rodič
revize
bb0380a905
1 změnil soubory, kde provedl 642 přidání a 0 odebrání
  1. 642 0
      index.mjs

+ 642 - 0
index.mjs

@@ -0,0 +1,642 @@
+/*
+ * md ast - pluggable markdown parser
+ */
+
+
+const syntax = {
+    a:{
+        paired: true,
+        recursive: false,
+        startRegexp: /\[(.*)\]\(/,
+        endRegexp: /\)/,
+        content: {
+            start: {
+                point: 'end',
+                offset: 0
+            },
+            end: {
+                point: 'start',
+                offset: -1
+            }
+        },
+        begin: 0,
+        forward: {
+            point: 'end', //start, startEnd, end, endEnd
+            offset: 1
+        },
+        title: {
+            //index: 1,
+            recursive: true,
+        },
+        onbuild(md, mdTags, buildAST){ //this = {tag: }
+
+        }
+    },
+    strike: {
+        paired: true,
+        recursive: true,
+        startRegexp: /\~\~\S.*/,
+        endRegexp:   /\~\~\W/,
+        content: {
+            start: {
+                point: 'start',
+                offset: 2
+            },
+            end: {
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 0,
+        forward: {
+            point: 'endEnd', //start, startEnd, end, endEnd
+            offset: -1
+        }
+    },
+    bold1: {
+        paired: true,
+        recursive: true,
+        startRegexp: /\*\*\S.*/,
+        endRegexp:   /\*\*\W/,
+        content: {
+            start: {
+                point: 'start',
+                offset: 2
+            },
+            end: {
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 0,
+        forward: {
+            point: 'endEnd', //start, startEnd, end, endEnd
+            offset: -1
+        }
+    },
+    bold2: {
+        paired: true,
+        recursive: true,
+        startRegexp:  /\s__\S.*/,
+        endRegexp:    /__\W/,
+        content: {
+            start: {
+                point: 'start',
+                offset: 3
+            },
+            end: {
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 1,
+        forward: {
+            point: 'endEnd', //start, startEnd, end, endEnd
+            offset: -1
+        }
+    },
+    italic1: {
+        paired: true,
+        recursive: true,
+        startRegexp: /\*\S.*/,
+        endRegexp:   /\S\*[^*]/,
+        content: {
+            start: {
+                point: 'start',
+                offset: 1
+            },
+            end: {
+                point: 'start',
+                offset: 1
+            }
+        },
+        begin: 0,
+        forward: {
+            point: 'endEnd', //start, startEnd, end, endEnd
+            offset: -1
+        }
+    },
+    italic2: {
+        paired: true,
+        recursive: true,
+        startRegexp:  /\s_\S.*/,
+        endRegexp:   /\S_\W/,
+        content: {
+            start: {
+                point: 'start',
+                offset: 2
+            },
+            end: {
+                point: 'start',
+                offset: 1
+            }
+        },
+        begin: 1,
+        forward: {
+            point: 'endEnd', //start, startEnd, end, endEnd
+            offset: -1
+        }
+    },
+    root: {
+        paired: true,
+        recursive: true,
+        startRegexp:  /^/,
+        endRegexp:   /$/,
+        content: {
+            start: {
+                point: 'start',
+                offset: 1
+            },
+            end: {
+                point: 'end',
+                offset: 0
+            }
+        },
+        begin: 0,
+        forward: {
+            point: 'endEnd', //start, startEnd, end, endEnd
+            offset: -1
+        }
+    },
+
+
+    heading6: {
+        paired: true,
+        recursive: true,
+        startRegexp: /\n######[ \t]*(.*)\n/,
+        endRegexp: /\n#\s/,
+        content: {
+            start: {
+                point: 'end',
+                offset: -1
+            },
+            end: {
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 0,
+        forward: {
+            point: 'end', //start, startEnd, end, endEnd
+            offset: 0
+        },
+        title: {
+            //index: 1,
+            recursive: true,
+        },
+        onbuild(md, mdTags, buildAST){ //this = {tag: }
+
+        }
+    },
+    heading5: {
+        paired: true,
+        recursive: true,
+        startRegexp: /\n#####[ \t]*(.*)\n/,
+        endRegexp: /\n#{1,5}\s/,
+        content: {
+            start: {
+                point: 'end',
+                offset: -1
+            },
+            end: {
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 0,
+        forward: {
+            point: 'end', //start, startEnd, end, endEnd
+            offset: 0
+        },
+        title: {
+            //index: 1,
+            recursive: true,
+        },
+        onbuild(md, mdTags, buildAST){ //this = {tag: }
+
+        }
+    },
+
+    heading4: {
+        paired: true,
+        recursive: true,
+        startRegexp: /\n####[ \t]*(.*)\n/,
+        endRegexp: /\n#{1,4}\s/,
+        content: {
+            start: {
+                point: 'end',
+                offset: -1
+            },
+            end: {
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 0,
+        forward: {
+            point: 'end', //start, startEnd, end, endEnd
+            offset: 0
+        },
+        title: {
+            //index: 1,
+            recursive: true,
+        },
+        onbuild(md, mdTags, buildAST){ //this = {tag: }
+
+        }
+    },
+
+    heading3: {
+        paired: true,
+        recursive: true,
+        startRegexp: /\n###[ \t]*(.*)\n/,
+        endRegexp: /\n#{1,3}\s/,
+        content: {
+            start: {
+                point: 'end',
+                offset: -1
+            },
+            end: {
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 0,
+        forward: {
+            point: 'end', //start, startEnd, end, endEnd
+            offset: 0
+        },
+        title: {
+            //index: 1,
+            recursive: true,
+        },
+        onbuild(md, mdTags, buildAST){ //this = {tag: }
+
+        }
+    },
+
+    heading2: {
+        paired: true,
+        recursive: true,
+        startRegexp: /\n##[ \t]*(.*)\n/,
+        endRegexp: /\n#{1,2}\s/,
+        content: {
+            start: {
+                point: 'end',
+                offset: -1
+            },
+            end: {
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 0,
+        forward: {
+            point: 'end', //start, startEnd, end, endEnd
+            offset: 0
+        },
+        title: {
+            //index: 1,
+            recursive: true,
+        },
+        onbuild(md, mdTags, buildAST){ //this = {tag: }
+
+        }
+    },
+
+    heading1: {
+        paired: true,
+        recursive: true,
+        startRegexp: /\n#[ \t]*(.*)\n/,
+        endRegexp: /\n#\s/,
+        content: {
+            start: {
+                point: 'end',
+                offset: -1
+            },
+            end: {
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: -1,
+        forward: {
+            point: 'end', //start, startEnd, end, endEnd
+            offset: 0
+        },
+        title: {
+            //index: 1,
+            recursive: true,
+        },
+        onbuild(md, mdTags, buildAST){ //this = {tag: }
+
+        }
+    },
+    code: {
+        paired: true,
+        recursive: false,
+        startRegexp: /`/,
+        endRegexp: /`/,
+        content: {
+            start: {
+                point: 'start',
+                offset: 1
+            },
+            end: {
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 0,
+        forward: {
+            point: 'end', //start, startEnd, end, endEnd
+            offset: 1
+        }
+    },
+    codeMultiLine: {
+        paired: true, 
+        recursive: false,
+        startRegexp: /\n```\s*\n/,
+        endRegexp: /\n```\s*\n/,
+        content:{
+            start:{
+                point: 'end',
+                offset: 0
+            },
+            end:{
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 1,
+        forward:  {
+            point: 'endEnd',
+            offset: 0
+        },
+    },
+    codeLanguage: {
+        paired: true, 
+        recursive: false,
+        startRegexp: /\n```(\w+)\s*\n/,
+        endRegexp: /\n```\s*\n/,
+        title: {
+            recursive: false
+        },
+        content:{
+            start:{
+                point: 'end',
+                offset: 0
+            },
+            end:{
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 1,
+        forward:  {
+            point: 'endEnd',
+            offset: 0
+        },
+    },
+    unOrderedList: {
+        indent: true,
+        childName: 'unOrderedListItem',
+        //paired: true,
+        recursive: true,
+        regexp: /-\s*\S/,
+        content:{
+            start:{
+                point: 'end',
+                offset: -1
+            },
+            end:{
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 1,
+        forward:  {
+            point: 'end',
+            offset: 0
+        }
+    },
+    orderedList: {
+        indent: true,
+        childName: 'orderedListItem',
+        //paired: true,
+        recursive: true,
+        regexp: /\d+\.\s*\S/,
+        content:{
+            start:{
+                point: 'end',
+                offset: -1
+            },
+            end:{
+                point: 'start',
+                offset: 0
+            }
+        },
+        begin: 1,
+        forward:  {
+            point: 'end',
+            offset: 0
+        }
+    }
+}
+
+const indentRegexp = (regexp,count) => new RegExp(`\\n([ \\t]${count === undefined ? '*' : `{${count}}` })` + regexp.toString().slice(1,-1))
+const indentEndRegexp = (count) => new RegExp(`\\n([ \\t]${count === undefined ? '*' : `{0,${count}}` })\\S`)
+
+function findNearest(md, mdTags, offset=0){
+    let nearest, nearestMatch = {index: Infinity};
+    for (let [mdTag, {paired, 
+                        startRegexp, 
+                        regexp, indent}] of  Object.entries(mdTags)) {
+        if (mdTag === 'root') continue;
+
+        regexp = startRegexp || regexp
+        regexp = indent ? indentRegexp(regexp) : regexp
+
+        let match = md.offsetMatch(offset,  regexp)
+        if (match && match.index < nearestMatch.index){
+            nearestMatch = match
+            nearest = mdTag
+        }
+    }
+    return [nearest, nearestMatch]
+}
+
+
+//node:
+//{
+//     tag: 'keyFromSyntax',
+//     children: [String,  Node]
+//     parent: node
+//}
+//
+String.prototype.offsetMatch = function(offset, ...params){
+    return this.slice(offset).match(...params)
+}
+
+Array.prototype.last = function(amount=-1){
+    return this[this.length +amount]
+}
+
+String.prototype.cutIndent = function(indent){
+    let lines = this.split('\n').map(line => line.slice(0, indent).match(/^\s*$/) ? line.slice(indent) : line)
+    return lines.join('\n')
+}
+
+function buildAST(md, mdTags=syntax, offset=0, tree={tag: 'root'}, stack=[]){
+    let currentNode     = stack.last() || tree
+    if (currentNode.tag === 'root') md = '\n' + md + '\n'
+    currentNode.children  = currentNode.children || []
+    const { children }     = currentNode
+
+    let {indent, childName, title, recursive, regexp, endRegexp, content: {end: {offset: offsetEnd, point} }, forward } = mdTags[currentNode.tag]
+
+    if (indent){
+        if (currentNode.parent.tag !== currentNode.tag){ //li
+            let { parent: {children: siblings} } = currentNode
+            if (siblings.length > 1 && siblings.last(-2).tag === currentNode.tag){
+                siblings.pop()
+                currentNode = siblings.last()
+            }
+            const { children }     = currentNode
+            const indentLength = currentNode.startMatch[1].length
+            console.log(indentLength)
+            currentNode.indentLength = indentLength
+
+            endRegexp = indentEndRegexp(indentLength)
+            let endMatch = md.offsetMatch(offset, endRegexp) || {index: md.length +1, 0: 'zzz'}
+
+            let listMD   = md.slice(offset, endMatch.index + offset).cutIndent(currentNode.startMatch[0].length -2)
+            debugger;
+
+            const newNode = {tag: childName, startOffset: offset, parent: currentNode, startMatch: currentNode.startMatch}
+            children.push(newNode)
+
+            newNode.children = buildAST(listMD, mdTags).children
+            newNode.children.forEach(item => item.parent = currentNode)
+
+            offset = newNode.endOffset = currentNode.endOffset = endMatch.index + offset
+        }
+    }
+    
+    if (title){
+        const {index=1, recursive} = title
+        const {[index]: titleContent } = currentNode.startMatch
+        if (titleContent && recursive){
+            currentNode.title = buildAST(titleContent, mdTags).children
+            currentNode.title.forEach(item => item.parent = currentNode)
+        }
+        else {
+            currentNode.title = [titleContent]
+        }
+    }
+
+    while(offset < md.length){
+        const [nearest, nearestMatch] = findNearest(md, mdTags, offset)
+        let endMatch = md.offsetMatch(offset, endRegexp)
+        if (!recursive || endMatch) { //if we (should) find closing tag 
+            if (!recursive || !nearest || endMatch.index <= nearestMatch.index ){ //if closing tag closer than new nested tag
+                endMatch = endMatch || {index: md.length - offset, 0: "zzz"}
+                currentNode.endContent = offset + endMatch.index + offsetEnd + (point === 'end' ? endMatch[0].length : 0)
+                offset !== currentNode.endContent && children.push(md.slice(offset, currentNode.endContent))
+                offset += endMatch.index + forward.offset + (forward.point === 'endEnd' ? endMatch[0].length : 0)
+                currentNode.endOffset = offset
+                currentNode.endMatch  = endMatch
+                return currentNode
+            }
+        }
+        if (nearest){ //new nested tag
+            const {begin,content: {start}} = mdTags[nearest]
+            if (nearestMatch.index){ //if just text before nested tag
+                nearestMatch.index + begin > 0 && children.push(md.slice(offset, offset + nearestMatch.index + begin))
+                offset += nearestMatch.index
+            }
+            else { //if  new tag right under cursor (offset)
+                let newNode = {tag: nearest, startOffset: offset, parent: currentNode, startMatch: nearestMatch}
+                children.push(newNode)
+                newNode = buildAST(md, mdTags, offset + start.offset + (start.point === 'end' ? nearestMatch[0].length : 0), tree, [...stack, newNode])
+                offset = newNode.endOffset
+            }
+        }
+        else { //no nearest - rest of line to children as text
+            children.push(md.slice(offset))
+            offset = md.length
+        }
+    }
+    return currentNode
+}
+
+const Heading = ({react:React, children, title, node: {tag}}) => {
+    const level = +tag.slice(-1)
+    const _ = React.createElement.bind(React)
+    if (isNaN(level)) throw new SyntaxError('wrong heading name')
+    return _(React.Fragment, null, 
+                _(`h${level}`, null, ...title),
+                _(`div`, null, ...children)
+            )
+}
+
+const A = ({react:React, children, title}) =>{
+    const _ = React.createElement.bind(React)
+    return _("a", {children: title, href: children})
+}
+
+const defaultMapMDToComponents = {
+    heading1: Heading,
+    heading2: Heading,
+    heading3: Heading,
+    heading4: Heading,
+    heading5: Heading,
+    heading6: Heading,
+    strike: "strike",
+    bold1: "strong",
+    bold2: "strong",
+    a: A, 
+    italic1: "i",
+    italic2: "i",
+    unOrderedList: 'ul',
+    orderedList: 'ol',
+    unOrderedListItem: 'li',
+    orderedListItem: 'li',
+    code: 'code',
+    codeMultiLine: 'pre',
+    codeLanguage: 'pre',
+    root: ""
+}
+
+function toReact(ast, React, mapMDToComponents=defaultMapMDToComponents){
+    const gC = (tag, c) => (c = mapMDToComponents[tag]) ? c : (c === "" ? React.Fragment : "span")
+    const RenderComponent = gC(ast.tag)
+    const _ = React.createElement.bind(React)
+    const childToReact = child => typeof child === 'string' ?  child  : 
+                                                               toReact(child, React, mapMDToComponents)
+    return _(RenderComponent, {node: ast, 
+                                key: Math.random(),
+                               children: ast.children.map(childToReact),
+                               title: ast.title && ast.title.map(childToReact),
+                               react: React})
+}
+
+//window.module && (module.exports = {
+    //buildAST,
+    //toReact
+//})
+
+//console.log(Object.keys(syntax))
+export {buildAST, toReact}
+
+
+
+//const md = 
+//`
+//# heading1
+//какой-то _текст_
+//# heading2
+//а тут **шо** цикавого?)))
+//`;
+//console.log( buildAST(md).children)
+