rittenhop-ghost/versions/5.94.2/node_modules/mobiledoc-dom-renderer/lib/renderers/0-3.js

398 lines
10 KiB
JavaScript
Raw Normal View History

import { createTextNode } from '../utils/dom';
import ImageCard from '../cards/image';
import RENDER_TYPE from '../utils/render-type';
import {
MARKUP_SECTION_TYPE,
IMAGE_SECTION_TYPE,
LIST_SECTION_TYPE,
CARD_SECTION_TYPE
} from '../utils/section-types';
import {
isValidSectionTagName,
isValidMarkerType
} from '../utils/tag-names';
import {
reduceAttributes
} from '../utils/sanitization-utils';
import {
defaultSectionElementRenderer,
defaultMarkupElementRenderer
} from '../utils/render-utils';
import {
MARKUP_MARKER_TYPE,
ATOM_MARKER_TYPE
} from '../utils/marker-types';
export const MOBILEDOC_VERSION_0_3_0 = '0.3.0';
export const MOBILEDOC_VERSION_0_3_1 = '0.3.1';
export const MOBILEDOC_VERSION_0_3_2 = '0.3.2';
const IMAGE_SECTION_TAG_NAME = 'img';
function validateVersion(version) {
switch (version) {
case MOBILEDOC_VERSION_0_3_0:
case MOBILEDOC_VERSION_0_3_1:
case MOBILEDOC_VERSION_0_3_2:
return;
default:
throw new Error(`Unexpected Mobiledoc version "${version}"`);
}
}
export default class Renderer {
constructor(mobiledoc, state) {
let {
cards,
cardOptions,
atoms,
unknownCardHandler,
unknownAtomHandler,
markupElementRenderer,
sectionElementRenderer,
dom
} = state;
let {
version,
sections,
atoms: atomTypes,
cards: cardTypes,
markups: markerTypes
} = mobiledoc;
validateVersion(version);
this.dom = dom;
this.root = this.dom.createDocumentFragment();
this.sections = sections;
this.atomTypes = atomTypes;
this.cardTypes = cardTypes;
this.markerTypes = markerTypes;
this.cards = cards;
this.atoms = atoms;
this.cardOptions = cardOptions;
this.unknownCardHandler = unknownCardHandler || this._defaultUnknownCardHandler;
this.unknownAtomHandler = unknownAtomHandler || this._defaultUnknownAtomHandler;
this.sectionElementRenderer = {
'__default__': defaultSectionElementRenderer
};
Object.keys(sectionElementRenderer).forEach(key => {
this.sectionElementRenderer[key.toLowerCase()] = sectionElementRenderer[key];
});
this.markupElementRenderer = {
'__default__': defaultMarkupElementRenderer
};
Object.keys(markupElementRenderer).forEach(key => {
this.markupElementRenderer[key.toLowerCase()] = markupElementRenderer[key];
});
this._renderCallbacks = [];
this._teardownCallbacks = [];
}
get _defaultUnknownCardHandler() {
return ({env: {name}}) => {
throw new Error(`Card "${name}" not found but no unknownCardHandler was registered`);
};
}
get _defaultUnknownAtomHandler() {
return ({env: {name}}) => {
throw new Error(`Atom "${name}" not found but no unknownAtomHandler was registered`);
};
}
render() {
this.sections.forEach(section => {
let rendered = this.renderSection(section);
if (rendered) {
this.root.appendChild(rendered);
}
});
for (let i=0; i < this._renderCallbacks.length; i++) {
this._renderCallbacks[i]();
}
// maintain a reference to child nodes so they can be cleaned up later by teardown
this._renderedChildNodes = Array.prototype.slice.call(this.root.childNodes);
return { result: this.root, teardown: () => this.teardown() };
}
teardown() {
for (let i=0; i < this._teardownCallbacks.length; i++) {
this._teardownCallbacks[i]();
}
for (let i=0; i < this._renderedChildNodes.length; i++) {
let node = this._renderedChildNodes[i];
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
}
renderSection(section) {
const [type] = section;
switch (type) {
case MARKUP_SECTION_TYPE:
return this.renderMarkupSection(section);
case IMAGE_SECTION_TYPE:
return this.renderImageSection(section);
case LIST_SECTION_TYPE:
return this.renderListSection(section);
case CARD_SECTION_TYPE:
return this.renderCardSection(section);
default:
throw new Error(`Cannot render mobiledoc section of type "${type}"`);
}
}
renderMarkersOnElement(element, markers) {
let elements = [element];
let currentElement = element;
let pushElement = (openedElement) => {
currentElement.appendChild(openedElement);
elements.push(openedElement);
currentElement = openedElement;
};
for (let i=0, l=markers.length; i<l; i++) {
let marker = markers[i];
let [type, openTypes, closeCount, value] = marker;
for (let j=0, m=openTypes.length; j<m; j++) {
let markerType = this.markerTypes[openTypes[j]];
let [tagName, attrs=[]] = markerType;
if (isValidMarkerType(tagName)) {
pushElement(this.renderMarkupElement(tagName, attrs));
} else {
closeCount--;
}
}
switch (type) {
case MARKUP_MARKER_TYPE:
currentElement.appendChild(createTextNode(this.dom, value));
break;
case ATOM_MARKER_TYPE:
currentElement.appendChild(this._renderAtom(value));
break;
default:
throw new Error(`Unknown markup type (${type})`);
}
for (let j=0, m=closeCount; j<m; j++) {
elements.pop();
currentElement = elements[elements.length - 1];
}
}
}
/**
* @param attrs Array
*/
renderMarkupElement(tagName, attrs) {
tagName = tagName.toLowerCase();
attrs = reduceAttributes(attrs);
let renderer = this.markupElementRendererFor(tagName);
return renderer(tagName, this.dom, attrs);
}
markupElementRendererFor(tagName) {
return this.markupElementRenderer[tagName] ||
this.markupElementRenderer.__default__;
}
renderListItem(markers) {
const element = this.dom.createElement('li');
this.renderMarkersOnElement(element, markers);
return element;
}
renderListSection([type, tagName, listItems]) {
if (!isValidSectionTagName(tagName, LIST_SECTION_TYPE)) {
return;
}
const element = this.dom.createElement(tagName);
listItems.forEach(li => {
element.appendChild(this.renderListItem(li));
});
return element;
}
renderImageSection([type, src]) {
let element = this.dom.createElement(IMAGE_SECTION_TAG_NAME);
element.src = src;
return element;
}
findCard(name) {
for (let i=0; i < this.cards.length; i++) {
if (this.cards[i].name === name) {
return this.cards[i];
}
}
if (name === ImageCard.name) {
return ImageCard;
}
return this._createUnknownCard(name);
}
_findCardByIndex(index) {
let cardType = this.cardTypes[index];
if (!cardType) {
throw new Error(`No card definition found at index ${index}`);
}
let [ name, payload ] = cardType;
let card = this.findCard(name);
return {
card,
payload
};
}
_createUnknownCard(name) {
return {
name,
type: RENDER_TYPE,
render: this.unknownCardHandler
};
}
_createCardArgument(card, payload={}) {
let env = {
name: card.name,
isInEditor: false,
dom: this.dom,
didRender: (callback) => this._registerRenderCallback(callback),
onTeardown: (callback) => this._registerTeardownCallback(callback)
};
let options = this.cardOptions;
return { env, options, payload };
}
_registerTeardownCallback(callback) {
this._teardownCallbacks.push(callback);
}
_registerRenderCallback(callback) {
this._renderCallbacks.push(callback);
}
renderCardSection([type, index]) {
let { card, payload } = this._findCardByIndex(index);
let cardArg = this._createCardArgument(card, payload);
let rendered = card.render(cardArg);
this._validateCardRender(rendered, card.name);
return rendered;
}
_validateCardRender(rendered, cardName) {
if (!rendered) {
return;
}
if (typeof rendered !== 'object') {
throw new Error(`Card "${cardName}" must render ${RENDER_TYPE}, but result was "${rendered}"`);
}
}
findAtom(name) {
for (let i=0; i < this.atoms.length; i++) {
if (this.atoms[i].name === name) {
return this.atoms[i];
}
}
return this._createUnknownAtom(name);
}
_createUnknownAtom(name) {
return {
name,
type: RENDER_TYPE,
render: this.unknownAtomHandler
};
}
_createAtomArgument(atom, value, payload) {
let env = {
name: atom.name,
isInEditor: false,
dom: this.dom,
onTeardown: (callback) => this._registerTeardownCallback(callback)
};
let options = this.cardOptions;
return { env, options, value, payload };
}
_validateAtomRender(rendered, atomName) {
if (!rendered) {
return;
}
if (typeof rendered !== 'object') {
throw new Error(`Atom "${atomName}" must render ${RENDER_TYPE}, but result was "${rendered}"`);
}
}
_findAtomByIndex(index) {
let atomType = this.atomTypes[index];
if (!atomType) {
throw new Error(`No atom definition found at index ${index}`);
}
let [ name, value, payload ] = atomType;
let atom = this.findAtom(name);
return {
atom,
value,
payload
};
}
_renderAtom(index) {
let { atom, value, payload } = this._findAtomByIndex(index);
let atomArg = this._createAtomArgument(atom, value, payload);
let rendered = atom.render(atomArg);
this._validateAtomRender(rendered, atom.name);
return rendered || createTextNode(this.dom, '');
}
renderMarkupSection([type, tagName, markers, attributes = []]) {
tagName = tagName.toLowerCase();
if (!isValidSectionTagName(tagName, MARKUP_SECTION_TYPE)) {
return;
}
let attrsObj = reduceAttributes(attributes);
let renderer = this.sectionElementRendererFor(tagName);
let element = renderer(tagName, this.dom, attrsObj);
this.renderMarkersOnElement(element, markers);
return element;
}
sectionElementRendererFor(tagName) {
return this.sectionElementRenderer[tagName] ||
this.sectionElementRenderer.__default__;
}
}