Created
May 16, 2017 18:13
-
-
Save Thinkscape/c9457a86b1c3346da5d14dca51b2fbc0 to your computer and use it in GitHub Desktop.
PM DOMParser allowing invokables for parseDOM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as dom from '../prosemirror/dom'; | |
import { | |
DOMParser as PMDOMParser, | |
ParseContext as PMParseContext, | |
NodeType, | |
ParseRule, | |
Node, | |
Mark | |
} from '../prosemirror'; | |
export interface InvokableParseRule extends ParseRule { | |
(el: dom.Node, parseContext: ParseContext): any; | |
result?: Node | Mark; | |
} | |
export class DOMParser extends PMDOMParser { | |
private invokables: Array<InvokableParseRule> = []; | |
constructor(schema, rules) { | |
super(schema, rules); | |
rules.forEach(rule => { | |
if (typeof rule === 'function') { | |
this.invokables.push(rule); | |
} | |
}); | |
} | |
parse(dom, options: { [key: string]: any} = {}) { | |
let context = new ParseContext(this, options, false); | |
context.addAll(dom, null, options.from, options.to); | |
return context.finish(); | |
} | |
matchTag(dom: dom.Node, context: ParseContext): ParseRule { | |
for (let i = 0; i < this.invokables.length; i++) { | |
let rule = this.invokables[i]; | |
const result = rule(dom, context); | |
if (result) { | |
rule.result = result; | |
return rule; | |
} | |
} | |
return this.matchTag(dom, context); | |
} | |
static schemaRules(schema) { | |
let result: Array<ParseRule> = []; | |
function insert(rule) { | |
let priority = rule.priority === null ? 50 : rule.priority; | |
let i = 0; | |
for (; i < result.length; i++) { | |
let next = result[i]; | |
let nextPriority = next.priority === null ? 50 : next.priority; | |
if (nextPriority! < priority) { | |
break; | |
} | |
} | |
result.splice(i, 0, rule); | |
} | |
function copy(obj) { | |
let aCopy; | |
if (typeof obj === 'function') { | |
aCopy = (...args) => obj.apply(obj, args); | |
} else { | |
aCopy = {}; | |
} | |
for (let prop in obj) { | |
aCopy[prop] = obj[prop]; | |
} | |
return aCopy; | |
} | |
for (let name in schema.marks) { | |
let rulesOrInvokable = schema.marks[name].spec.parseDOM; | |
if (rulesOrInvokable) { | |
if (typeof rulesOrInvokable === 'function') { | |
insert(rulesOrInvokable = copy(rulesOrInvokable)); | |
rulesOrInvokable.mark = name; | |
} else { | |
rulesOrInvokable.forEach(rule => { | |
insert(rule = copy(rule)); | |
rule.mark = name; | |
}); | |
} | |
} | |
} | |
for (let name in schema.nodes) { | |
let rulesOrInvokable = schema.nodes[name].spec.parseDOM; | |
if (rulesOrInvokable) { | |
if (typeof rulesOrInvokable === 'function') { | |
insert(rulesOrInvokable = copy(rulesOrInvokable)); | |
rulesOrInvokable.node = name; | |
} else { | |
rulesOrInvokable.forEach(rule => { | |
insert(rule = copy(rule)); | |
rule.node = name; | |
}); | |
} | |
} | |
} | |
return result; | |
} | |
static fromSchema(schema) { | |
return schema.cached.domParser || | |
(schema.cached.domParser = new DOMParser(schema, DOMParser.schemaRules(schema))); | |
} | |
} | |
export class ParseContext extends PMParseContext { | |
/** | |
* This method inherits most of original body, but adds a check for rule.result | |
*/ | |
addElementByRule(dom: dom.Node, rule: InvokableParseRule) { | |
let sync; | |
let nodeType; | |
let markType; | |
let before; | |
let mark; | |
let contentDOM: Element | null | string | undefined | dom.Node; | |
if (rule.node) { | |
// A node | |
const nodeType = this.parser.schema.nodes[rule.node] as NodeType; | |
if (nodeType.isLeaf) { | |
if (rule.result) { | |
this.insertNode(rule.result as Node); | |
} else { | |
this.insertNode(nodeType.create(rule.attrs, null, this.marks)); | |
} | |
} else { | |
sync = this.enter(nodeType, rule.attrs!, rule.preserveWhitespace as boolean) && this.top; | |
} | |
} else { | |
// A mark | |
markType = this.parser.schema.marks[rule.mark!]; | |
if (rule.result) { | |
mark = rule.result as Mark; | |
} else { | |
mark = markType.create(rule.attrs); | |
} | |
before = this.addMark(mark); | |
} | |
if (nodeType && nodeType.isLeaf) { | |
this.findInside(dom); | |
} else if (rule.getContent) { | |
this.findInside(dom); | |
rule.getContent(dom).forEach(node => this.insertNode(mark ? node.mark(mark.addToSet(node.marks)) : node)); | |
} else { | |
contentDOM = rule.contentElement; | |
if (typeof contentDOM === 'string') { | |
contentDOM = (dom as dom.Element).querySelector(contentDOM); | |
} | |
if (!contentDOM) { | |
contentDOM = dom; | |
} | |
this.findAround(dom, contentDOM, true); | |
this.addAll(contentDOM, sync); | |
} | |
if (sync) { | |
this.sync(sync); | |
this.open--; | |
} else if (before) { | |
this.marks = before; | |
} | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment