74 lines
3.2 KiB
JavaScript
74 lines
3.2 KiB
JavaScript
|
|
import {defaultSchema} from "./_module.mjs";
|
||
|
|
import DOMParser from "./dom-parser.mjs";
|
||
|
|
import StringSerializer from "./string-serializer.mjs";
|
||
|
|
import { Slice } from "prosemirror-model";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Use the DOM and ProseMirror's DOMParser to construct a ProseMirror document state from an HTML string. This cannot be
|
||
|
|
* used server-side.
|
||
|
|
* @param {string} htmlString A string of HTML.
|
||
|
|
* @param {Schema} [schema] The ProseMirror schema to use instead of the default one.
|
||
|
|
* @returns {Node} The document node.
|
||
|
|
*/
|
||
|
|
export function parseHTMLString(htmlString, schema) {
|
||
|
|
const target = document.createElement("template");
|
||
|
|
target.innerHTML = htmlString;
|
||
|
|
return DOMParser.fromSchema(schema ?? defaultSchema).parse(target.content);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Use the StringSerializer to convert a ProseMirror document into an HTML string. This can be used server-side.
|
||
|
|
* @param {Node} doc The ProseMirror document.
|
||
|
|
* @param {object} [options] Additional options to configure serialization behavior.
|
||
|
|
* @param {Schema} [options.schema] The ProseMirror schema to use instead of the default one.
|
||
|
|
* @param {string|number} [options.spaces] The number of spaces to use for indentation. See {@link StringNode#toString}
|
||
|
|
* for details.
|
||
|
|
* @returns {string}
|
||
|
|
*/
|
||
|
|
export function serializeHTMLString(doc, {schema, spaces}={}) {
|
||
|
|
schema = schema ?? defaultSchema;
|
||
|
|
// If the only content is an empty <p></p> tag, return an empty string.
|
||
|
|
if ( (doc.size < 3) && (doc.content[0].type === schema.nodes.paragraph) ) return "";
|
||
|
|
return StringSerializer.fromSchema(schema).serializeFragment(doc.content).toString(spaces);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @callback ProseMirrorSliceTransformer
|
||
|
|
* @param {Node} node The candidate node.
|
||
|
|
* @returns {Node|void} A new node to replace the candidate node, or nothing if a replacement should not be made.
|
||
|
|
*/
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Apply a transformation to some nodes in a slice, and return the new slice.
|
||
|
|
* @param {Slice} slice The slice to transform.
|
||
|
|
* @param {function} transformer The transformation function.
|
||
|
|
* @returns {Slice} Either the original slice if no changes were made, or the newly-transformed slice.
|
||
|
|
*/
|
||
|
|
export function transformSlice(slice, transformer) {
|
||
|
|
const nodeTree = new Map();
|
||
|
|
slice.content.nodesBetween(0, slice.content.size, (node, start, parent, index) => {
|
||
|
|
nodeTree.set(node, { parent, index });
|
||
|
|
});
|
||
|
|
let newSlice;
|
||
|
|
const replaceNode = (node, { parent, index }) => {
|
||
|
|
// If there is a parent, make the replacement, then recurse up the tree to the root, creating new nodes as we go.
|
||
|
|
if ( parent ) {
|
||
|
|
const newContent = parent.content.replaceChild(index, node);
|
||
|
|
const newParent = parent.copy(newContent);
|
||
|
|
replaceNode(newParent, nodeTree.get(parent));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Otherwise, handle replacing the root slice's content.
|
||
|
|
const targetSlice = newSlice ?? slice;
|
||
|
|
const fragment = targetSlice.content;
|
||
|
|
const newFragment = fragment.replaceChild(index, node);
|
||
|
|
newSlice = new Slice(newFragment, targetSlice.openStart, targetSlice.openEnd);
|
||
|
|
}
|
||
|
|
for ( const [node, treeInfo] of nodeTree.entries() ) {
|
||
|
|
const newNode = transformer(node);
|
||
|
|
if ( newNode ) replaceNode(newNode, treeInfo);
|
||
|
|
}
|
||
|
|
return newSlice ?? slice;
|
||
|
|
}
|