Skip to content

Instantly share code, notes, and snippets.

@Thinkscape
Created May 16, 2017 18:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Thinkscape/c9457a86b1c3346da5d14dca51b2fbc0 to your computer and use it in GitHub Desktop.
Save Thinkscape/c9457a86b1c3346da5d14dca51b2fbc0 to your computer and use it in GitHub Desktop.
PM DOMParser allowing invokables for parseDOM
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